]> mj.ucw.cz Git - paperjam.git/blob - parse.cc
Bits of commands
[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_double : public arg_val {
158   double val;
159 public:
160   bool given() { return true; }
161   explicit operator double () { return val; }
162   arg_double(double x) { val = x; }
163   string dump() { return to_string(val); }
164 };
165
166 class arg_string : public arg_val {
167   string val;
168 public:
169   bool given() { return true; }
170   explicit operator string () { return val; }
171   arg_string(string x) { val = x; }
172   string dump() { return '"' + val + '"'; }
173 };
174
175 static arg_val null_arg;
176
177 /*** Parser ***/
178
179 struct unit {
180   const char *name;
181   double multiplier;
182 };
183
184 #define MM (72/25.4)
185
186 static const unit units[] = {
187   { "mm",       MM },
188   { "cm",       10*MM },
189   { "dm",       100*MM },
190   { "m",        1000*MM },
191   { "in",       72 },
192   { "pt",       1 },
193   { NULL,       0 }
194 };
195
196 static double parse_dimen(const arg_def *adef)
197 {
198   token_type t = next_token();
199   if (t != TOK_NUMBER)
200     parse_error("Parameter %s must be a dimension", adef->name);
201   double tmp = token_num;
202
203   t = next_token();
204   if (t != TOK_IDENT)
205     parse_error("Parameter %s must have a unit", adef->name);
206   for (uint i=0; units[i].name; i++)
207     if (token == units[i].name)
208       return tmp * units[i].multiplier;
209   parse_error("Unknown unit %s", token.c_str());
210 }
211
212 static void parse_pipeline(cmd *c)
213 {
214   pipeline *pp = new pipeline;
215   next_token();
216
217   while (peek_token() != TOK_CLOSE_BRACE)
218     {
219       if (pp->branches.size() && next_token() != TOK_COMMA)
220         parse_error("Comma expected between pipeline branches");
221
222       pipeline_branch *pb = new pipeline_branch;
223       pp->branches.push_back(pb);
224
225       for (;;)
226         {
227           token_type t = next_token();
228           if (t == TOK_CLOSE_BRACE || t == TOK_END)
229             parse_error("Premature end of pipeline");
230           if (t == TOK_COLON)
231             break;
232
233           if (pb->selectors.size())
234             {
235               if (t != TOK_COMMA)
236                 parse_error("Invalid pipeline selector");
237               t = next_token();
238               if (t == TOK_CLOSE_BRACE || t == TOK_END)
239                 parse_error("Premature end of pipeline");
240             }
241
242           pipeline_selector ps;
243           if (t != TOK_NUMBER)
244             parse_error("Pipeline selectors must start with a number");
245           if (!token_is_int())
246             parse_error("Pipeline selectors must be integers");
247           ps.from = (int) token_num;
248           ps.to = ps.from;
249
250           if (peek_token() == TOK_DOTDOT)
251             {
252               next_token();
253               t = next_token();
254               if (t != TOK_NUMBER)
255                 parse_error("Pipeline selectors must be numbers or ranges");
256               if (!token_is_int())
257                 parse_error("Pipeline selectors must be integers");
258               ps.to = (int) token_num;
259             }
260
261           pb->selectors.push_back(ps);
262         }
263
264       parse_commands(&pb->commands);
265     }
266
267   c->pipe = pp;
268   next_token();
269 }
270
271 static void parse_args(cmd *c)
272 {
273   const cmd_def *cdef = c->def;
274   const arg_def *adefs = cdef->arg_defs;
275   uint num_args = 0;
276   while (adefs[num_args].name)
277     num_args++;
278
279   c->args.resize(num_args, &null_arg);
280
281   token_type t = next_token();
282   if (t != TOK_OPEN_PAREN)
283     {
284       return_token();
285       return;
286     }
287
288   bool saw_named = false;
289   uint next_pos = 0;
290   for (;;)
291     {
292       t = next_token();
293       uint argi = 0;
294       if (t == TOK_IDENT)
295         {
296           while (adefs[argi].name && token != adefs[argi].name)
297             argi++;
298           if (!adefs[argi].name)
299             parse_error("Command %s has no parameter %s", cdef->name, token.c_str());
300           if (c->args.at(argi)->given())
301             parse_error("Parameter %s given multiple times", token.c_str());
302           t = next_token();
303           if (t != TOK_EQUAL)
304             parse_error("Parameter name must be followed by '='");
305           saw_named = true;
306         }
307       else if (saw_named)
308         parse_error("Positional parameters must precede named ones");
309       else
310         {
311           return_token();
312           while (next_pos < num_args && !(adefs[next_pos].type & AT_POSITIONAL))
313             next_pos++;
314           if (next_pos >= num_args)
315             parse_error("Too many positional arguments for command %s", cdef->name);
316           argi = next_pos++;
317         }
318
319       const arg_def *adef = &adefs[argi];
320       arg_val *val = NULL;
321       switch (adef->type & AT_TYPE_MASK)
322         {
323         case AT_STRING:
324           t = next_token();
325           if (t != TOK_STRING)
326             parse_error("Parameter %s must be a string", adef->name);
327           val = new arg_string(token);
328           break;
329         case AT_DOUBLE:
330           t = next_token();
331           if (t != TOK_NUMBER)
332             parse_error("Parameter %s must be a number", adef->name);
333           val = new arg_double(token_num);
334           break;
335         case AT_DIMEN:
336           val = new arg_double(parse_dimen(adef));
337           break;
338         default:
339           abort();
340         }
341
342       c->args.at(argi) = val;
343
344       t = next_token();
345       if (t == TOK_CLOSE_PAREN)
346         break;
347       if (t != TOK_COMMA)
348         parse_error("Comma expected after parameter %s", adef->name);
349     }
350
351   for (uint i=0; i<num_args; i++)
352     if ((adefs[i].type & AT_MANDATORY) && !c->args.at(i)->given())
353       parse_error("Command %s is missing a parameter %s", cdef->name, adefs[i].name);
354 }
355
356 static void debug_cmd(cmd *c, uint indent=0)
357 {
358   printf("%*sCommand %s\n", indent, "", c->def->name);
359   for (size_t i=0; i < c->args.size(); i++)
360     {
361       const arg_def *adef = &c->def->arg_defs[i];
362       string dump = c->args.at(i)->dump();
363       printf("%*sArg #%d: %s = %s\n", indent+4, "", (int) i, adef->name, dump.c_str());
364     }
365   if (c->pipe)
366     {
367       printf("%*sPipeline:\n", indent+4, "");
368       for (auto pb: c->pipe->branches)
369         {
370           printf("%*sSelector:\n", indent+8, "");
371           for (auto ps: pb->selectors)
372             printf("%*s%d - %d\n", indent+12, "", ps.from, ps.to);
373           printf("%*sCommands:\n", indent+8, "");
374           for (auto cc: pb->commands)
375             debug_cmd(cc, indent+12);
376         }
377     }
378 }
379
380 static void debug_cmds(list<cmd *> *cmds)
381 {
382   for (auto c: *cmds)
383     debug_cmd(c);
384 }
385
386 static cmd *parse_cmd()
387 {
388   const cmd_def *cdef = cmd_table;
389   while (cdef->name && token != cdef->name)
390     cdef++;
391   if (!cdef->name)
392     parse_error("Unknown command %s", token.c_str());
393
394   cmd *c = new cmd;
395   c->def = cdef;
396   c->pipe = NULL;
397
398   parse_args(c);
399
400   if (peek_token() == TOK_OPEN_BRACE)
401     {
402       if (!cdef->has_pipeline)
403         parse_error("Command %s does not accept a pipeline", cdef->name);
404       parse_pipeline(c);
405     }
406   else if (cdef->has_pipeline)
407     parse_error("Command %s requires a pipeline", cdef->name);
408
409   return c;
410 }
411
412 static void parse_commands(list<cmd *> *cmds)
413 {
414   for (;;)
415     {
416       token_type t = next_token();
417       if (t != TOK_IDENT)
418         {
419           return_token();
420           return;
421         }
422
423       cmd *c = parse_cmd();
424       cmds->push_back(c);
425     }
426 }
427
428 static void instantiate(list<cmd *> *cmds)
429 {
430   for (auto c: *cmds)
431     {
432       c->exec = c->def->constructor(c);
433       if (c->pipe)
434         {
435           for (auto pb: c->pipe->branches)
436             instantiate(&pb->commands);
437         }
438     }
439 }
440
441 void parse(const char *in, list<cmd *> *cmds)
442 {
443   in_pos = in;
444   parse_commands(cmds);
445   if (next_token() != TOK_END)
446     parse_error("Extra tokens after commands");
447
448   debug_cmds(cmds);
449   instantiate(cmds);
450 }