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