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}