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