main shrubtools / nviz / scan.c
  1#include <ctype.h>
  2#include <stdarg.h>
  3#include <stdbool.h>
  4#include <stdio.h>
  5#include <stdlib.h>
  6#include <string.h>
  7#include "scan.h"
  8#include "util.h"
  9
 10struct evalstring **paths;
 11size_t npaths;
 12static struct buffer buf;
 13
 14void
 15scaninit(struct scanner *s, const char *path)
 16{
 17	s->path = path;
 18	s->line = 1;
 19	s->col = 1;
 20	s->f = fopen(path, "r");
 21	if (!s->f)
 22		fatal("open %s:", path);
 23	s->chr = getc(s->f);
 24}
 25
 26void
 27scanclose(struct scanner *s)
 28{
 29	fclose(s->f);
 30}
 31
 32void
 33scanerror(struct scanner *s, const char *fmt, ...)
 34{
 35	extern const char *argv0;
 36	va_list ap;
 37
 38	fprintf(stderr, "%s: %s:%d:%d: ", argv0, s->path, s->line, s->col);
 39	va_start(ap, fmt);
 40	vfprintf(stderr, fmt, ap);
 41	va_end(ap);
 42	putc('\n', stderr);
 43	exit(1);
 44}
 45
 46static int
 47next(struct scanner *s)
 48{
 49	if (s->chr == '\n') {
 50		++s->line;
 51		s->col = 1;
 52	} else {
 53		++s->col;
 54	}
 55	s->chr = getc(s->f);
 56
 57	return s->chr;
 58}
 59
 60static int
 61issimplevar(int c)
 62{
 63	return isalnum(c) || c == '_' || c == '-';
 64}
 65
 66static int
 67isvar(int c)
 68{
 69	return issimplevar(c) || c == '.';
 70}
 71
 72static bool
 73newline(struct scanner *s)
 74{
 75	switch (s->chr) {
 76	case '\r':
 77		if (next(s) != '\n')
 78			scanerror(s, "expected '\\n' after '\\r'");
 79		/* fallthrough */
 80	case '\n':
 81		next(s);
 82		return true;
 83	}
 84	return false;
 85}
 86
 87static bool
 88singlespace(struct scanner *s)
 89{
 90	switch (s->chr) {
 91	case '$':
 92		next(s);
 93		if (newline(s))
 94			return true;
 95		ungetc(s->chr, s->f);
 96		s->chr = '$';
 97		return false;
 98	case ' ':
 99		next(s);
100		return true;
101	}
102	return false;
103}
104
105static bool
106space(struct scanner *s)
107{
108	if (!singlespace(s))
109		return false;
110	while (singlespace(s))
111		;
112	return true;
113}
114
115static bool
116comment(struct scanner *s)
117{
118	if (s->chr != '#')
119		return false;
120	do next(s);
121	while (!newline(s));
122	return true;
123}
124
125static void
126name(struct scanner *s)
127{
128	buf.len = 0;
129	while (isvar(s->chr)) {
130		bufadd(&buf, s->chr);
131		next(s);
132	}
133	if (!buf.len)
134		scanerror(s, "expected name");
135	bufadd(&buf, '\0');
136	space(s);
137}
138
139int
140scankeyword(struct scanner *s, char **var)
141{
142	/* must stay in sorted order */
143	static const struct {
144		const char *name;
145		int value;
146	} keywords[] = {
147		{"build",    BUILD},
148		{"default",  DEFAULT},
149		{"include",  INCLUDE},
150		{"pool",     POOL},
151		{"rule",     RULE},
152		{"subninja", SUBNINJA},
153	};
154	int low = 0, high = countof(keywords) - 1, mid, cmp;
155
156	for (;;) {
157		switch (s->chr) {
158		case ' ':
159			space(s);
160			if (!comment(s) && !newline(s))
161				scanerror(s, "unexpected indent");
162			break;
163		case '#':
164			comment(s);
165			break;
166		case '\r':
167		case '\n':
168			newline(s);
169			break;
170		case EOF:
171			return EOF;
172		default:
173			name(s);
174			while (low <= high) {
175				mid = (low + high) / 2;
176				cmp = strcmp(buf.data, keywords[mid].name);
177				if (cmp == 0)
178					return keywords[mid].value;
179				if (cmp < 0)
180					high = mid - 1;
181				else
182					low = mid + 1;
183			}
184			*var = xmemdup(buf.data, buf.len);
185			return VARIABLE;
186		}
187	}
188}
189
190char *
191scanname(struct scanner *s)
192{
193	name(s);
194	return xmemdup(buf.data, buf.len);
195}
196
197static void
198addstringpart(struct evalstring ***end, bool var)
199{
200	struct evalstring *p;
201
202	p = xmalloc(sizeof(*p));
203	p->next = NULL;
204	**end = p;
205	if (var) {
206		bufadd(&buf, '\0');
207		p->var = xmemdup(buf.data, buf.len);
208	} else {
209		p->var = NULL;
210		p->str = mkstr(buf.len);
211		memcpy(p->str->s, buf.data, buf.len);
212		p->str->s[buf.len] = '\0';
213	}
214	*end = &p->next;
215	buf.len = 0;
216}
217
218static void
219escape(struct scanner *s, struct evalstring ***end)
220{
221	switch (s->chr) {
222	case '$':
223	case ' ':
224	case ':':
225		bufadd(&buf, s->chr);
226		next(s);
227		break;
228	case '{':
229		if (buf.len > 0)
230			addstringpart(end, false);
231		while (isvar(next(s)))
232			bufadd(&buf, s->chr);
233		if (s->chr != '}')
234			scanerror(s, "invalid variable name");
235		next(s);
236		addstringpart(end, true);
237		break;
238	case '\r':
239	case '\n':
240		newline(s);
241		space(s);
242		break;
243	default:
244		if (buf.len > 0)
245			addstringpart(end, false);
246		while (issimplevar(s->chr)) {
247			bufadd(&buf, s->chr);
248			next(s);
249		}
250		if (!buf.len)
251			scanerror(s, "invalid $ escape");
252		addstringpart(end, true);
253	}
254}
255
256struct evalstring *
257scanstring(struct scanner *s, bool path)
258{
259	struct evalstring *str = NULL, **end = &str;
260
261	buf.len = 0;
262	for (;;) {
263		switch (s->chr) {
264		case '$':
265			next(s);
266			escape(s, &end);
267			break;
268		case ':':
269		case '|':
270		case ' ':
271			if (path)
272				goto out;
273			/* fallthrough */
274		default:
275			bufadd(&buf, s->chr);
276			next(s);
277			break;
278		case '\r':
279		case '\n':
280		case EOF:
281			goto out;
282		}
283	}
284out:
285	if (buf.len > 0)
286		addstringpart(&end, 0);
287	if (path)
288		space(s);
289	return str;
290}
291
292void
293scanpaths(struct scanner *s)
294{
295	static size_t max;
296	struct evalstring *str;
297
298	while ((str = scanstring(s, true))) {
299		if (npaths == max) {
300			max = max ? max * 2 : 32;
301			paths = xreallocarray(paths, max, sizeof(paths[0]));
302		}
303		paths[npaths++] = str;
304	}
305}
306
307void
308scanchar(struct scanner *s, int c)
309{
310	if (s->chr != c)
311		scanerror(s, "expected '%c'", c);
312	next(s);
313	space(s);
314}
315
316int
317scanpipe(struct scanner *s, int n)
318{
319	if (s->chr != '|')
320		return 0;
321	next(s);
322	if (s->chr != '|') {
323		if (!(n & 1))
324			scanerror(s, "expected '||'");
325		space(s);
326		return 1;
327	}
328	if (!(n & 2))
329		scanerror(s, "unexpected '||'");
330	next(s);
331	space(s);
332	return 2;
333}
334
335bool
336scanindent(struct scanner *s)
337{
338	bool indent;
339
340	for (;;) {
341		indent = space(s);
342		if (!comment(s))
343			return indent && !newline(s);
344	}
345}
346
347void
348scannewline(struct scanner *s)
349{
350	if (!newline(s))
351		scanerror(s, "expected newline");
352}