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