]> mj.ucw.cz Git - paperjam.git/blob - parse.cc
Simplify passing of arguments
[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 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   token_type t = next_token();
291   if (t != TOK_OPEN_PAREN)
292     {
293       return_token();
294       return;
295     }
296
297   bool saw_named = false;
298   uint next_pos = 0;
299   for (;;)
300     {
301       t = next_token();
302       if (t == TOK_CLOSE_PAREN)
303         break;
304       uint argi = 0;
305       bool has_value = false;
306       if (t == TOK_IDENT)
307         {
308           while (adefs[argi].name && token != adefs[argi].name)
309             argi++;
310           if (!adefs[argi].name)
311             parse_error("Command %s has no parameter %s", cdef->name, token.c_str());
312           if (c->args.count(token))
313             parse_error("Parameter %s given multiple times", token.c_str());
314           t = next_token();
315           if (t == TOK_EQUAL)
316             has_value = true;
317           else
318             return_token();
319           saw_named = true;
320         }
321       else if (saw_named)
322         parse_error("Positional parameters must precede named ones");
323       else
324         {
325           return_token();
326           while (next_pos < num_args && !(adefs[next_pos].type & AT_POSITIONAL))
327             next_pos++;
328           if (next_pos >= num_args)
329             parse_error("Too many positional arguments for command %s", cdef->name);
330           argi = next_pos++;
331           has_value = true;
332         }
333
334       const arg_def *adef = &adefs[argi];
335       uint type = adef->type & AT_TYPE_MASK;
336       arg_val *val = NULL;
337       if (has_value)
338         {
339           switch (type)
340             {
341             case AT_STRING:
342               t = next_token();
343               if (t != TOK_STRING)
344                 parse_error("Parameter %s must be a string", adef->name);
345               val = new arg_string(token);
346               break;
347             case AT_INT:
348               t = next_token();
349               if (t != TOK_NUMBER || !token_is_int())
350                 parse_error("Parameter %s must be an integer", adef->name);
351               val = new arg_int((int) token_num);
352               break;
353             case AT_DOUBLE:
354               t = next_token();
355               if (t != TOK_NUMBER)
356                 parse_error("Parameter %s must be a number", adef->name);
357               val = new arg_double(token_num);
358               break;
359             case AT_DIMEN:
360               val = new arg_double(parse_dimen(adef));
361               break;
362             case AT_SWITCH:
363               t = next_token();
364               if (t != TOK_NUMBER || !token_is_int() || ((int) token_num != 0 && (int) token_num != 1))
365                 parse_error("Parameter %s must be a switch", adef->name);
366               val = new arg_int((int) token_num);
367               break;
368             default:
369               abort();
370             }
371         }
372       else
373         {
374           if (type == AT_SWITCH)
375             val = new arg_int(1);
376           else
377             parse_error("Parameter %s must have a value", adef->name);
378         }
379
380       c->args[adef->name] = val;
381
382       t = next_token();
383       if (t == TOK_CLOSE_PAREN)
384         break;
385       if (t != TOK_COMMA)
386         parse_error("Comma expected after parameter %s", adef->name);
387     }
388
389   for (uint i=0; i<num_args; i++)
390     if ((adefs[i].type & AT_MANDATORY) && !c->args.count(adefs[i].name))
391       parse_error("Command %s is missing a parameter %s", cdef->name, adefs[i].name);
392 }
393
394 static void debug_cmd(cmd *c, uint indent=0)
395 {
396   printf("%*sCommand %s\n", indent, "", c->def->name);
397   for (uint i=0; c->def->arg_defs[i].name; i++)
398     {
399       const arg_def *adef = &c->def->arg_defs[i];
400       string dump = c->arg(adef->name)->dump();
401       printf("%*sArg #%d: %s = %s\n", indent+4, "", (int) i, adef->name, dump.c_str());
402     }
403   if (c->pipe)
404     {
405       printf("%*sPipeline:\n", indent+4, "");
406       for (auto pb: c->pipe->branches)
407         {
408           printf("%*sSelector:\n", indent+8, "");
409           for (auto ps: pb->selectors)
410             printf("%*s%d - %d\n", indent+12, "", ps.from, ps.to);
411           printf("%*sCommands:\n", indent+8, "");
412           for (auto cc: pb->commands)
413             debug_cmd(cc, indent+12);
414         }
415     }
416 }
417
418 static void debug_cmds(list<cmd *> &cmds)
419 {
420   for (auto c: cmds)
421     debug_cmd(c);
422 }
423
424 static cmd *parse_cmd()
425 {
426   const cmd_def *cdef = cmd_table;
427   while (cdef->name && token != cdef->name)
428     cdef++;
429   if (!cdef->name)
430     parse_error("Unknown command %s", token.c_str());
431
432   cmd *c = new cmd;
433   c->def = cdef;
434   c->pipe = NULL;
435
436   parse_args(c);
437
438   if (peek_token() == TOK_OPEN_BRACE)
439     {
440       if (!cdef->has_pipeline)
441         parse_error("Command %s does not accept a pipeline", cdef->name);
442       parse_pipeline(c);
443     }
444   else if (cdef->has_pipeline)
445     parse_error("Command %s requires a pipeline", cdef->name);
446
447   return c;
448 }
449
450 static void parse_commands(list<cmd *> &cmds)
451 {
452   for (;;)
453     {
454       token_type t = next_token();
455       if (t != TOK_IDENT)
456         {
457           return_token();
458           return;
459         }
460
461       cmd *c = parse_cmd();
462       cmds.push_back(c);
463     }
464 }
465
466 static void instantiate(list<cmd *> &cmds)
467 {
468   for (auto c: cmds)
469     {
470       c->exec = c->def->constructor(c);
471       if (c->pipe)
472         {
473           for (auto pb: c->pipe->branches)
474             instantiate(pb->commands);
475         }
476     }
477 }
478
479 void parse(const char *in, list<cmd *> &cmds)
480 {
481   in_pos = in;
482   parse_commands(cmds);
483   if (next_token() != TOK_END)
484     parse_error("Extra tokens after commands");
485
486   debug_cmds(cmds);
487   instantiate(cmds);
488 }
489
490 void parser_help()
491 {
492   for (int i=0; cmd_table[i].name; i++)
493     {
494       const cmd_def *def = &cmd_table[i];
495       printf("%s\n", def->name);
496
497       const arg_def *arg = def->arg_defs;
498       static const char * const type_names[] = {
499         "string", "int", "double", "dimen", "switch"
500       };
501       while (arg->name)
502         {
503           printf("\t%s (%s)%s%s\n",
504             arg->name,
505             type_names[arg->type & AT_TYPE_MASK],
506             (arg->type & AT_MANDATORY) ? " [mandatory]" : "",
507             (arg->type & AT_POSITIONAL) ? " [positional]" : "");
508           arg++;
509         }
510
511         if (def->has_pipeline)
512           printf("\t{ pipeline }\n");
513     }
514 }