]> mj.ucw.cz Git - paperjam.git/blob - parse.cc
2e38d3ad403204451ea2ace07e6a8d3079c78b57
[paperjam.git] / parse.cc
1 #include <cassert>
2 #include <cstdarg>
3 #include <cstdlib>
4 #include <cstdio>
5
6 #include "jam.h"
7
8 /*** Lexer ***/
9
10 enum token_type {
11   TOK_NONE,
12   TOK_END,
13   TOK_EQUAL,
14   TOK_COMMA,
15   TOK_COLON,
16   TOK_DOTDOT,
17   TOK_OPEN_PAREN,
18   TOK_CLOSE_PAREN,
19   TOK_OPEN_BRACE,
20   TOK_CLOSE_BRACE,
21   TOK_IDENT,
22   TOK_STRING,
23   TOK_NUMBER,
24 };
25
26 const char *in_pos;
27 static token_type this_token = TOK_NONE;
28 static token_type buffered_token = TOK_NONE;
29 static string token;
30 static double token_num;
31
32 static void NONRET parse_error(const char *msg, ...);
33 static void parse_commands(list<cmd *> &cmds);
34
35 static void parse_error(const char *msg, ...)
36 {
37   va_list args;
38   va_start(args, msg);
39   fprintf(stderr, "Parse error: ");
40   vfprintf(stderr, msg, args);
41   fprintf(stderr, "\n");
42   va_end(args);
43   exit(1);
44 }
45
46 static token_type get_next_token()
47 {
48   while (*in_pos == ' ' || *in_pos == '\t' || *in_pos == '\r' || *in_pos == '\n')
49     in_pos++;
50
51   token = "";
52   if (!*in_pos)
53     return TOK_END;
54
55   if (*in_pos >= '0' && *in_pos <= '9' ||
56       *in_pos == '-' && in_pos[1] >= '0' && in_pos[1] <= '9')
57     {
58       token += *in_pos++;
59       while (*in_pos >= '0' && *in_pos <= '9' || *in_pos == '.' && in_pos[1] != '.')
60         token += *in_pos++;
61
62       size_t end_pos;
63       token_num = stod(token, &end_pos);
64       if (end_pos < token.length())
65         parse_error("Invalid number %s", token.c_str());
66       return TOK_NUMBER;
67     }
68
69   if (*in_pos >= 'A' && *in_pos <= 'Z' ||
70       *in_pos >= 'a' && *in_pos <= 'z')
71     {
72       while (*in_pos >= 'A' && *in_pos <= 'Z' ||
73              *in_pos >= 'a' && *in_pos <= 'z' ||
74              *in_pos >= '0' && *in_pos <= '9')
75         token += *in_pos++;
76       return TOK_IDENT;
77     }
78
79   if (*in_pos == '"')
80     {
81       in_pos++;
82       while (*in_pos != '"')
83         {
84           if (!*in_pos)
85             parse_error("Unterminated string");
86           if (*in_pos == '\\')
87             {
88               in_pos++;
89               if (*in_pos != '"' && *in_pos != '\\')
90                 parse_error("Unrecognized escape sequence \\%c", *in_pos);
91             }
92           token += *in_pos++;
93         }
94       in_pos++;
95       return TOK_STRING;
96     }
97
98   uint c = *in_pos++;
99   switch (c)
100     {
101     case '=':
102       return TOK_EQUAL;
103     case ',':
104       return TOK_COMMA;
105     case ':':
106       return TOK_COLON;
107     case '(':
108       return TOK_OPEN_PAREN;
109     case ')':
110       return TOK_CLOSE_PAREN;
111     case '{':
112       return TOK_OPEN_BRACE;
113     case '}':
114       return TOK_CLOSE_BRACE;
115     default:
116       if (c == '.' && *in_pos == '.')
117         {
118           in_pos++;
119           return TOK_DOTDOT;
120         }
121       parse_error("Unrecognized character '%c'", c);
122     }
123 }
124
125 static token_type next_token()
126 {
127   if (buffered_token != TOK_NONE)
128     this_token = buffered_token;
129   else
130     this_token = get_next_token();
131   buffered_token = TOK_NONE;
132   return this_token;
133 }
134
135 static void return_token()
136 {
137   assert(this_token != TOK_NONE);
138   assert(buffered_token == TOK_NONE);
139   buffered_token = this_token;
140   this_token = TOK_NONE;
141 }
142
143 static token_type peek_token()
144 {
145   next_token();
146   return_token();
147   return buffered_token;
148 }
149
150 static bool token_is_int()
151 {
152   return token_num == (double)(int) token_num;
153 }
154
155 /*** Argument types ***/
156
157 class arg_int : public arg_val {
158   int val;
159 public:
160   arg_int(int x) { val = x; }
161   bool given() { return true; }
162   int as_int(int def UNUSED) { return val; }
163   string dump() { return "<int> " + to_string(val); }
164 };
165
166 class arg_double : public arg_val {
167   double val;
168 public:
169   arg_double(double x) { val = x; }
170   bool given() { return true; }
171   double as_double(double def UNUSED) { return val; }
172   string dump() { return "<double> " + to_string(val); }
173 };
174
175 class arg_string : public arg_val {
176   string val;
177 public:
178   arg_string(string x) { val = x; }
179   bool given() { return true; }
180   const string as_string(string def UNUSED) { return val; }
181   string dump() { return "<string> \"" + val + '"'; }
182 };
183
184 static arg_val null_arg;
185
186 /*** Parser ***/
187
188 struct unit {
189   const char *name;
190   double multiplier;
191 };
192
193 #define MM (72/25.4)
194
195 static const unit units[] = {
196   { "mm",       MM },
197   { "cm",       10*MM },
198   { "dm",       100*MM },
199   { "m",        1000*MM },
200   { "in",       72 },
201   { "pt",       1 },
202   { NULL,       0 }
203 };
204
205 static double parse_dimen(const arg_def *adef)
206 {
207   token_type t = next_token();
208   if (t != TOK_NUMBER)
209     parse_error("Parameter %s must be a dimension", adef->name);
210   double tmp = token_num;
211
212   t = next_token();
213   if (t != TOK_IDENT)
214     parse_error("Parameter %s must have a unit", adef->name);
215   for (uint i=0; units[i].name; i++)
216     if (token == units[i].name)
217       return tmp * units[i].multiplier;
218   parse_error("Unknown unit %s", token.c_str());
219 }
220
221 static void parse_pipeline(cmd *c)
222 {
223   pipeline *pp = new pipeline;
224   next_token();
225
226   while (peek_token() != TOK_CLOSE_BRACE)
227     {
228       if (pp->branches.size() && next_token() != TOK_COMMA)
229         parse_error("Comma expected between pipeline branches");
230
231       pipeline_branch *pb = new pipeline_branch;
232       pp->branches.push_back(pb);
233
234       token_type t;
235       for (;;)
236         {
237           t = next_token();
238           if (t == TOK_END)
239             parse_error("Missing close brace");
240           if (t == TOK_CLOSE_BRACE || t == TOK_COLON || t == TOK_COMMA)
241             break;
242
243           pipeline_selector ps;
244           if (t != TOK_NUMBER)
245             parse_error("Pipeline selectors must start with a number");
246           if (!token_is_int())
247             parse_error("Pipeline selectors must be integers");
248           ps.from = (int) token_num;
249           ps.to = ps.from;
250
251           if (peek_token() == TOK_DOTDOT)
252             {
253               next_token();
254               t = next_token();
255               if (t != TOK_NUMBER)
256                 parse_error("Pipeline selectors must be numbers or ranges");
257               if (!token_is_int())
258                 parse_error("Pipeline selectors must be integers");
259               ps.to = (int) token_num;
260             }
261
262           pb->selectors.push_back(ps);
263         }
264
265       if (t == TOK_COLON)
266         parse_commands(pb->commands);
267       else
268         return_token();
269     }
270
271   c->pipe = pp;
272   next_token();
273 }
274
275 static void parse_args(cmd *c)
276 {
277   const cmd_def *cdef = c->def;
278   const arg_def *adefs = cdef->arg_defs;
279   uint num_args = 0;
280   while (adefs[num_args].name)
281     num_args++;
282
283   c->args.resize(num_args, &null_arg);
284
285   token_type t = next_token();
286   if (t != TOK_OPEN_PAREN)
287     {
288       return_token();
289       return;
290     }
291
292   bool saw_named = false;
293   uint next_pos = 0;
294   for (;;)
295     {
296       t = next_token();
297       uint argi = 0;
298       if (t == TOK_IDENT)
299         {
300           while (adefs[argi].name && token != adefs[argi].name)
301             argi++;
302           if (!adefs[argi].name)
303             parse_error("Command %s has no parameter %s", cdef->name, token.c_str());
304           if (c->args.at(argi)->given())
305             parse_error("Parameter %s given multiple times", token.c_str());
306           t = next_token();
307           if (t != TOK_EQUAL)
308             parse_error("Parameter name must be followed by '='");
309           saw_named = true;
310         }
311       else if (saw_named)
312         parse_error("Positional parameters must precede named ones");
313       else
314         {
315           return_token();
316           while (next_pos < num_args && !(adefs[next_pos].type & AT_POSITIONAL))
317             next_pos++;
318           if (next_pos >= num_args)
319             parse_error("Too many positional arguments for command %s", cdef->name);
320           argi = next_pos++;
321         }
322
323       const arg_def *adef = &adefs[argi];
324       arg_val *val = NULL;
325       switch (adef->type & AT_TYPE_MASK)
326         {
327         case AT_STRING:
328           t = next_token();
329           if (t != TOK_STRING)
330             parse_error("Parameter %s must be a string", adef->name);
331           val = new arg_string(token);
332           break;
333         case AT_INT:
334           t = next_token();
335           if (t != TOK_NUMBER || !token_is_int())
336             parse_error("Parameter %s must be an integer", adef->name);
337           val = new arg_int((int) token_num);
338           break;
339         case AT_DOUBLE:
340           t = next_token();
341           if (t != TOK_NUMBER)
342             parse_error("Parameter %s must be a number", adef->name);
343           val = new arg_double(token_num);
344           break;
345         case AT_DIMEN:
346           val = new arg_double(parse_dimen(adef));
347           break;
348         default:
349           abort();
350         }
351
352       c->args.at(argi) = val;
353
354       t = next_token();
355       if (t == TOK_CLOSE_PAREN)
356         break;
357       if (t != TOK_COMMA)
358         parse_error("Comma expected after parameter %s", adef->name);
359     }
360
361   for (uint i=0; i<num_args; i++)
362     if ((adefs[i].type & AT_MANDATORY) && !c->args.at(i)->given())
363       parse_error("Command %s is missing a parameter %s", cdef->name, adefs[i].name);
364 }
365
366 static void debug_cmd(cmd *c, uint indent=0)
367 {
368   printf("%*sCommand %s\n", indent, "", c->def->name);
369   for (size_t i=0; i < c->args.size(); i++)
370     {
371       const arg_def *adef = &c->def->arg_defs[i];
372       string dump = c->args.at(i)->dump();
373       printf("%*sArg #%d: %s = %s\n", indent+4, "", (int) i, adef->name, dump.c_str());
374     }
375   if (c->pipe)
376     {
377       printf("%*sPipeline:\n", indent+4, "");
378       for (auto pb: c->pipe->branches)
379         {
380           printf("%*sSelector:\n", indent+8, "");
381           for (auto ps: pb->selectors)
382             printf("%*s%d - %d\n", indent+12, "", ps.from, ps.to);
383           printf("%*sCommands:\n", indent+8, "");
384           for (auto cc: pb->commands)
385             debug_cmd(cc, indent+12);
386         }
387     }
388 }
389
390 static void debug_cmds(list<cmd *> &cmds)
391 {
392   for (auto c: cmds)
393     debug_cmd(c);
394 }
395
396 static cmd *parse_cmd()
397 {
398   const cmd_def *cdef = cmd_table;
399   while (cdef->name && token != cdef->name)
400     cdef++;
401   if (!cdef->name)
402     parse_error("Unknown command %s", token.c_str());
403
404   cmd *c = new cmd;
405   c->def = cdef;
406   c->pipe = NULL;
407
408   parse_args(c);
409
410   if (peek_token() == TOK_OPEN_BRACE)
411     {
412       if (!cdef->has_pipeline)
413         parse_error("Command %s does not accept a pipeline", cdef->name);
414       parse_pipeline(c);
415     }
416   else if (cdef->has_pipeline)
417     parse_error("Command %s requires a pipeline", cdef->name);
418
419   return c;
420 }
421
422 static void parse_commands(list<cmd *> &cmds)
423 {
424   for (;;)
425     {
426       token_type t = next_token();
427       if (t != TOK_IDENT)
428         {
429           return_token();
430           return;
431         }
432
433       cmd *c = parse_cmd();
434       cmds.push_back(c);
435     }
436 }
437
438 static void instantiate(list<cmd *> &cmds)
439 {
440   for (auto c: cmds)
441     {
442       c->exec = c->def->constructor(c);
443       if (c->pipe)
444         {
445           for (auto pb: c->pipe->branches)
446             instantiate(pb->commands);
447         }
448     }
449 }
450
451 void parse(const char *in, list<cmd *> &cmds)
452 {
453   in_pos = in;
454   parse_commands(cmds);
455   if (next_token() != TOK_END)
456     parse_error("Extra tokens after commands");
457
458   debug_cmds(cmds);
459   instantiate(cmds);
460 }