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