2 * PaperJam -- Command parser
4 * (c) 2018 Martin Mares <mj@ucw.cz>
33 static token_type this_token = TOK_NONE;
34 static token_type buffered_token = TOK_NONE;
36 static double token_num;
38 static void NONRET parse_error(const char *msg, ...);
39 static void parse_commands(list<cmd *> &cmds);
41 static void parse_error(const char *msg, ...)
45 fprintf(stderr, "Parse error: ");
46 vfprintf(stderr, msg, args);
47 fprintf(stderr, "\n");
52 static token_type get_next_token()
54 while (*in_pos == ' ' || *in_pos == '\t' || *in_pos == '\r' || *in_pos == '\n')
61 if (*in_pos >= '0' && *in_pos <= '9' ||
62 *in_pos == '-' && in_pos[1] >= '0' && in_pos[1] <= '9')
65 while (*in_pos >= '0' && *in_pos <= '9' || *in_pos == '.' && in_pos[1] != '.')
69 token_num = stod(token, &end_pos);
70 if (end_pos < token.length())
71 parse_error("Invalid number %s", token.c_str());
75 if (*in_pos >= 'A' && *in_pos <= 'Z' ||
76 *in_pos >= 'a' && *in_pos <= 'z')
78 while (*in_pos >= 'A' && *in_pos <= 'Z' ||
79 *in_pos >= 'a' && *in_pos <= 'z' ||
80 *in_pos >= '0' && *in_pos <= '9' ||
90 while (*in_pos != '"')
93 parse_error("Unterminated string");
97 if (*in_pos != '"' && *in_pos != '\\')
98 parse_error("Unrecognized escape sequence \\%c", *in_pos);
116 return TOK_OPEN_PAREN;
118 return TOK_CLOSE_PAREN;
120 return TOK_OPEN_BRACE;
122 return TOK_CLOSE_BRACE;
124 if (c == '.' && *in_pos == '.')
129 parse_error("Unrecognized character '%c'", c);
133 static token_type next_token()
135 if (buffered_token != TOK_NONE)
136 this_token = buffered_token;
138 this_token = get_next_token();
139 buffered_token = TOK_NONE;
143 static void return_token()
145 assert(this_token != TOK_NONE);
146 assert(buffered_token == TOK_NONE);
147 buffered_token = this_token;
148 this_token = TOK_NONE;
151 static token_type peek_token()
155 return buffered_token;
158 static bool token_is_int()
160 return token_num == (double)(int) token_num;
163 /*** Argument types ***/
165 class arg_int : public arg_val {
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); }
174 class arg_double : public arg_val {
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); }
183 class arg_string : public arg_val {
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 + '"'; }
203 static const unit units[] = {
213 static double parse_dimen(const arg_def *adef)
215 token_type t = next_token();
217 parse_error("Parameter %s must be a dimension", adef->name);
218 double tmp = token_num;
228 parse_error("Parameter %s must have a unit", adef->name);
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());
236 static void parse_pipeline(cmd *c)
238 pipeline *pp = new pipeline;
241 while (peek_token() != TOK_CLOSE_BRACE)
243 if (pp->branches.size() && next_token() != TOK_COMMA)
244 parse_error("Comma expected between pipeline branches");
246 pipeline_branch *pb = new pipeline_branch;
247 pp->branches.push_back(pb);
254 parse_error("Missing close brace");
255 if (t == TOK_CLOSE_BRACE || t == TOK_COLON || t == TOK_COMMA)
258 pipeline_selector ps;
260 parse_error("Pipeline selectors must start with a number");
262 parse_error("Pipeline selectors must be integers");
263 ps.from = (int) token_num;
266 if (peek_token() == TOK_DOTDOT)
271 parse_error("Pipeline selectors must be numbers or ranges");
273 parse_error("Pipeline selectors must be integers");
274 ps.to = (int) token_num;
277 pb->selectors.push_back(ps);
281 parse_commands(pb->commands);
290 static void parse_args(cmd *c)
292 const cmd_def *cdef = c->def;
293 const arg_def *adefs = cdef->arg_defs;
295 while (adefs[num_args].name)
298 token_type t = next_token();
299 if (t != TOK_OPEN_PAREN)
305 bool saw_named = false;
310 if (t == TOK_CLOSE_PAREN)
313 bool has_value = false;
316 while (adefs[argi].name && token != adefs[argi].name)
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());
330 parse_error("Positional parameters must precede named ones");
334 while (next_pos < num_args && !(adefs[next_pos].type & AT_POSITIONAL))
336 if (next_pos >= num_args)
337 parse_error("Too many positional arguments for command %s", cdef->name);
342 const arg_def *adef = &adefs[argi];
343 uint type = adef->type & AT_TYPE_MASK;
352 parse_error("Parameter %s must be a string", adef->name);
353 val = new arg_string(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);
364 parse_error("Parameter %s must be a number", adef->name);
365 val = new arg_double(token_num);
368 val = new arg_double(parse_dimen(adef));
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);
382 if (type == AT_SWITCH)
383 val = new arg_int(1);
385 parse_error("Parameter %s must have a value", adef->name);
388 c->args[adef->name] = val;
391 if (t == TOK_CLOSE_PAREN)
394 parse_error("Comma expected after parameter %s", adef->name);
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);
402 static void debug_cmd(cmd *c, uint indent=0)
404 printf("%*sCommand %s\n", indent, "", c->def->name);
405 for (uint i=0; c->def->arg_defs[i].name; i++)
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());
413 printf("%*sPipeline:\n", indent+4, "");
414 for (auto pb: c->pipe->branches)
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);
426 static void debug_cmds(list<cmd *> &cmds)
432 static cmd *parse_cmd()
434 const cmd_def *cdef = cmd_table;
435 while (cdef->name && token != cdef->name)
438 parse_error("Unknown command %s", token.c_str());
446 if (peek_token() == TOK_OPEN_BRACE)
448 if (!cdef->has_pipeline)
449 parse_error("Command %s does not accept a pipeline", cdef->name);
452 else if (cdef->has_pipeline)
453 parse_error("Command %s requires a pipeline", cdef->name);
458 static void parse_commands(list<cmd *> &cmds)
462 token_type t = next_token();
469 cmd *c = parse_cmd();
474 static void instantiate(list<cmd *> &cmds)
480 c->exec = c->def->constructor(c);
484 die("Error in %s: %s", c->def->name, e.what());
488 for (auto pb: c->pipe->branches)
489 instantiate(pb->commands);
494 void parse(const char *in, list<cmd *> &cmds)
496 debug("### Parsing commands");
498 parse_commands(cmds);
499 if (next_token() != TOK_END)
500 parse_error("Extra tokens after commands");
509 for (int i=0; cmd_table[i].name; i++)
511 const cmd_def *def = &cmd_table[i];
512 printf("%s\n", def->name);
514 const arg_def *arg = def->arg_defs;
515 static const char * const type_names[] = {
516 "string", "int", "double", "dimen", "switch"
520 printf("\t%s (%s)%s%s\n",
522 type_names[arg->type & AT_TYPE_MASK],
523 (arg->type & AT_MANDATORY) ? " [mandatory]" : "",
524 (arg->type & AT_POSITIONAL) ? " [positional]" : "");
528 if (def->has_pipeline)
529 printf("\t{ pipeline }\n");