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