main shrub/shrubtools / proto / parse.c
  1#include <expat.h>
  2#include <alloc.h>
  3#include <byte.h>
  4#include <scan.h>
  5#include <str.h>
  6#include <stralloc.h>
  7#include <buffer.h>
  8#include <strerr.h>
  9#include <stdbool.h>
 10#include <stdio.h>
 11#include <unistd.h>
 12
 13#include "parse.h"
 14#include "util.h"
 15
 16enum parse_scope {
 17	SCOPE_PROTOCOL,
 18	SCOPE_INTERFACE,
 19	SCOPE_MESSAGE,
 20	SCOPE_ENUM,
 21	SCOPE_ENTRY,
 22	SCOPE_DESCRIPTION,
 23};
 24
 25struct parser {
 26	const char *path;
 27	XML_Parser xml;
 28	struct protocol *proto;
 29	struct interface *iface;
 30	struct message *msg;
 31	struct enumdef *en;
 32	struct enumentry *entry;
 33	enum parse_scope stack[32];
 34	size_t depth;
 35	struct description *desc;
 36	stralloc text;
 37};
 38
 39static const char *attr(const char **, const char *);
 40static int parseint(const char *, const char *);
 41static void pushscope(struct parser *, enum parse_scope);
 42static void popscope(struct parser *);
 43static bool inscope(const struct parser *, enum parse_scope);
 44static void cleartext(struct parser *);
 45static void appendtext(struct parser *, const char *, int);
 46static char *normalizedtext(const char *);
 47static void onstart(void *, const char *, const char **);
 48static void onend(void *, const char *);
 49static void ontext(void *, const XML_Char *, int);
 50extern const char *argv0;
 51
 52static void
 53die_path_line(const char *path, unsigned long line, const char *msg)
 54{
 55	buffer_puts(buffer_2, argv0);
 56	buffer_puts(buffer_2, ": ");
 57	buffer_puts(buffer_2, path);
 58	buffer_puts(buffer_2, ":");
 59	put_ulong(buffer_2, line);
 60	buffer_puts(buffer_2, ": ");
 61	buffer_puts(buffer_2, msg);
 62	buffer_puts(buffer_2, "\n");
 63	buffer_flush(buffer_2);
 64	_exit(1);
 65}
 66
 67static void
 68die_path_line_col(const char *path, unsigned long line, unsigned long col,
 69    const char *msg)
 70{
 71	buffer_puts(buffer_2, argv0);
 72	buffer_puts(buffer_2, ": ");
 73	buffer_puts(buffer_2, path);
 74	buffer_puts(buffer_2, ":");
 75	put_ulong(buffer_2, line);
 76	buffer_puts(buffer_2, ":");
 77	put_ulong(buffer_2, col);
 78	buffer_puts(buffer_2, ": ");
 79	buffer_puts(buffer_2, msg);
 80	buffer_puts(buffer_2, "\n");
 81	buffer_flush(buffer_2);
 82	_exit(1);
 83}
 84
 85static const char *
 86attr(const char **attrs, const char *name)
 87{
 88	size_t i;
 89
 90	for (i = 0; attrs && attrs[i]; i += 2) {
 91		if (str_equal(attrs[i], name))
 92			return attrs[i + 1];
 93	}
 94	return NULL;
 95}
 96
 97static int
 98parseint(const char *what, const char *s)
 99{
100	char *msg;
101	unsigned long v;
102	unsigned int n;
103
104	if (!s)
105		strerr_die4x(111, argv0, ": missing ", what, " attribute");
106	n = scan_ulong(s, &v);
107	if (*s == '\0' || s[n] != '\0' || v > 2147483647UL) {
108		msg = copystr(s);
109		strerr_die6x(111, argv0, ": invalid ", what, ": ", msg, "");
110	}
111	return (int)v;
112}
113
114static void
115pushscope(struct parser *p, enum parse_scope scope)
116{
117	if (p->depth == countof(p->stack))
118		strerr_die4x(111, argv0, ": ", p->path, ": XML nesting too deep");
119	p->stack[p->depth++] = scope;
120}
121
122static void
123popscope(struct parser *p)
124{
125	if (p->depth > 0)
126		--p->depth;
127}
128
129static bool
130inscope(const struct parser *p, enum parse_scope scope)
131{
132	return p->depth > 0 && p->stack[p->depth - 1] == scope;
133}
134
135static void
136cleartext(struct parser *p)
137{
138	alloc_free(p->text.s);
139	p->text.s = NULL;
140	p->text.len = 0;
141	p->text.a = 0;
142}
143
144static void
145appendtext(struct parser *p, const char *s, int n)
146{
147	size_t need;
148
149	if (n <= 0)
150		return;
151	need = p->text.len + (size_t)n + 1;
152	if (!stralloc_ready(&p->text, need))
153		die_nomem();
154	byte_copy(p->text.s + p->text.len, (unsigned int)n, (char *)s);
155	p->text.len += (size_t)n;
156	p->text.s[p->text.len] = '\0';
157}
158
159static char *
160normalizedtext(const char *s)
161{
162	const char *start, *end, *lineend;
163	stralloc out = {0};
164	size_t n;
165
166	if (!s)
167		return NULL;
168	start = s;
169	while (*start == '\n' || *start == '\r' || *start == '\t' || *start == ' ')
170		++start;
171	end = s + str_len(s);
172	while (end > start && (end[-1] == '\n' || end[-1] == '\r' || end[-1] == '\t' || end[-1] == ' '))
173		--end;
174	if (start == end)
175		return NULL;
176
177	while (start < end) {
178		while (start < end && (*start == '\n' || *start == '\r'))
179			++start;
180		lineend = start;
181		while (lineend < end && *lineend != '\n' && *lineend != '\r')
182			++lineend;
183		while (start < lineend && (*start == ' ' || *start == '\t'))
184			++start;
185		while (lineend > start && (lineend[-1] == ' ' || lineend[-1] == '\t'))
186			--lineend;
187		n = (size_t)(lineend - start);
188		if (n > 0) {
189			if (out.len > 0 && !stralloc_catb(&out, "\n", 1))
190				goto nomem;
191			if (!stralloc_catb(&out, start, (unsigned int)n))
192				goto nomem;
193		}
194		start = lineend;
195		while (start < end && *start != '\n' && *start != '\r')
196			++start;
197	}
198	if (out.len == 0) {
199		alloc_free(out.s);
200		return NULL;
201	}
202	if (!stralloc_0(&out))
203		goto nomem;
204	return out.s;
205
206nomem:
207	alloc_free(out.s);
208	die_nomem();
209	return NULL;
210}
211
212static void
213onstart(void *data, const char *name, const char **attrs)
214{
215	struct parser *p = data;
216	const char *aname, *atype, *iface, *allow_null, *version, *type, *summary;
217
218	if (str_equal(name, "protocol")) {
219		if (p->proto->name)
220			die_path_line(p->path, XML_GetCurrentLineNumber(p->xml),
221			    "duplicate protocol element");
222		aname = attr(attrs, "name");
223		if (!aname)
224			die_path_line(p->path, XML_GetCurrentLineNumber(p->xml),
225			    "protocol missing name");
226		p->proto->name = copystr(aname);
227		pushscope(p, SCOPE_PROTOCOL);
228		return;
229	}
230
231	if (str_equal(name, "interface") && inscope(p, SCOPE_PROTOCOL)) {
232		aname = attr(attrs, "name");
233		version = attr(attrs, "version");
234		if (!aname || !version)
235			die_path_line(p->path, XML_GetCurrentLineNumber(p->xml),
236			    "interface missing name/version");
237		p->iface = proto_add_interface(p->proto, aname, parseint("version", version));
238		pushscope(p, SCOPE_INTERFACE);
239		return;
240	}
241
242	if ((str_equal(name, "request") || str_equal(name, "event")) && inscope(p, SCOPE_INTERFACE)) {
243		aname = attr(attrs, "name");
244		type = attr(attrs, "type");
245		if (!aname)
246			die_path_line(p->path, XML_GetCurrentLineNumber(p->xml),
247			    "message missing name");
248		p->msg = iface_add_message(p->iface,
249			str_equal(name, "request") ? MSG_REQUEST : MSG_EVENT, aname);
250		p->msg->destructor = type && str_equal(type, "destructor");
251		pushscope(p, SCOPE_MESSAGE);
252		return;
253	}
254
255	if (str_equal(name, "arg") && inscope(p, SCOPE_MESSAGE)) {
256		aname = attr(attrs, "name");
257		atype = attr(attrs, "type");
258		iface = attr(attrs, "interface");
259		allow_null = attr(attrs, "allow-null");
260		summary = attr(attrs, "summary");
261		if (!aname || !atype)
262			die_path_line(p->path, XML_GetCurrentLineNumber(p->xml),
263			    "arg missing name/type");
264		message_add_arg(p->msg, aname, atype, iface,
265			allow_null && str_equal(allow_null, "true"));
266		if (summary)
267			p->msg->args[p->msg->nargs - 1].summary = copystr(summary);
268		return;
269	}
270
271	if (str_equal(name, "enum") && inscope(p, SCOPE_INTERFACE)) {
272		aname = attr(attrs, "name");
273		if (!aname)
274			die_path_line(p->path, XML_GetCurrentLineNumber(p->xml),
275			    "enum missing name");
276		iface_add_enum(p->iface, aname);
277		p->en = iface_last_enum(p->iface);
278		pushscope(p, SCOPE_ENUM);
279		return;
280	}
281
282	if (str_equal(name, "entry") && inscope(p, SCOPE_ENUM)) {
283		const char *value;
284
285		aname = attr(attrs, "name");
286		value = attr(attrs, "value");
287		summary = attr(attrs, "summary");
288		if (!aname || !value)
289			die_path_line(p->path, XML_GetCurrentLineNumber(p->xml),
290			    "entry missing name/value");
291		enum_add_entry(p->en, aname, value);
292		p->entry = enum_last_entry(p->en);
293		if (summary)
294			p->entry->summary = copystr(summary);
295		pushscope(p, SCOPE_ENTRY);
296		return;
297	}
298
299	if (str_equal(name, "description")) {
300		if (inscope(p, SCOPE_MESSAGE))
301			p->desc = &p->msg->desc;
302		else if (inscope(p, SCOPE_ENTRY))
303			p->desc = &p->entry->desc;
304		else if (inscope(p, SCOPE_ENUM))
305			p->desc = &p->en->desc;
306		else if (inscope(p, SCOPE_INTERFACE))
307			p->desc = &p->iface->desc;
308		else if (inscope(p, SCOPE_PROTOCOL))
309			p->desc = &p->proto->desc;
310		else
311			return;
312		summary = attr(attrs, "summary");
313		alloc_free(p->desc->summary);
314		p->desc->summary = summary ? copystr(summary) : NULL;
315		cleartext(p);
316		pushscope(p, SCOPE_DESCRIPTION);
317		return;
318	}
319}
320
321static void
322onend(void *data, const char *name)
323{
324	struct parser *p = data;
325
326	if (str_equal(name, "request") || str_equal(name, "event")) {
327		if (inscope(p, SCOPE_MESSAGE)) {
328			p->msg = NULL;
329			popscope(p);
330		}
331		return;
332	}
333	if (str_equal(name, "enum")) {
334		if (inscope(p, SCOPE_ENUM))
335		{
336			p->en = NULL;
337			popscope(p);
338		}
339		return;
340	}
341	if (str_equal(name, "entry")) {
342		if (inscope(p, SCOPE_ENTRY)) {
343			p->entry = NULL;
344			popscope(p);
345		}
346		return;
347	}
348	if (str_equal(name, "description")) {
349		if (inscope(p, SCOPE_DESCRIPTION)) {
350			alloc_free(p->desc->text);
351			p->desc->text = normalizedtext(p->text.s);
352			p->desc = NULL;
353			cleartext(p);
354			popscope(p);
355		}
356		return;
357	}
358	if (str_equal(name, "interface")) {
359		if (inscope(p, SCOPE_INTERFACE)) {
360			p->iface = NULL;
361			popscope(p);
362		}
363		return;
364	}
365	if (str_equal(name, "protocol")) {
366		if (inscope(p, SCOPE_PROTOCOL))
367			popscope(p);
368		return;
369	}
370}
371
372static void
373ontext(void *data, const XML_Char *s, int len)
374{
375	struct parser *p = data;
376
377	if (inscope(p, SCOPE_DESCRIPTION))
378		appendtext(p, s, len);
379}
380
381void
382parsefile(const char *path, struct protocol *proto)
383{
384	struct parser p;
385	FILE *fp;
386	void *buf;
387	int done;
388
389	byte_zero(&p, sizeof(p));
390	p.path = path;
391	p.proto = proto;
392	p.xml = XML_ParserCreate(NULL);
393	if (!p.xml)
394		strerr_die2sys(111, argv0, ": XML_ParserCreate");
395	XML_SetUserData(p.xml, &p);
396	XML_SetElementHandler(p.xml, onstart, onend);
397	XML_SetCharacterDataHandler(p.xml, ontext);
398
399	fp = fopen(path, "rb");
400	if (!fp) {
401		XML_ParserFree(p.xml);
402		strerr_die4sys(111, argv0, ": open ", path, "");
403	}
404
405	done = 0;
406	while (!done) {
407		size_t nread;
408
409		buf = XML_GetBuffer(p.xml, 4096);
410		if (!buf)
411			strerr_die4x(111, argv0, ": ", path, ": XML_GetBuffer failed");
412		nread = fread(buf, 1, 4096, fp);
413		done = nread < 4096;
414		if (ferror(fp))
415			strerr_die4sys(111, argv0, ": read ", path, "");
416		if (XML_ParseBuffer(p.xml, (int)nread, done) == XML_STATUS_ERROR) {
417			die_path_line_col(path,
418			    XML_GetCurrentLineNumber(p.xml),
419			    XML_GetCurrentColumnNumber(p.xml),
420			    XML_ErrorString(XML_GetErrorCode(p.xml)));
421		}
422	}
423
424	fclose(fp);
425	XML_ParserFree(p.xml);
426	cleartext(&p);
427
428	if (!proto->name)
429		strerr_die4x(111, argv0, ": ", path, ": missing protocol element");
430	proto_resolve(proto);
431}