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