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 parse_commands(list<cmd *> &cmds);
40 static token_type get_next_token()
42 while (*in_pos == ' ' || *in_pos == '\t' || *in_pos == '\r' || *in_pos == '\n')
49 if (*in_pos >= '0' && *in_pos <= '9' ||
50 *in_pos == '-' && in_pos[1] >= '0' && in_pos[1] <= '9')
53 while (*in_pos >= '0' && *in_pos <= '9' || *in_pos == '.' && in_pos[1] != '.')
57 token_num = stod(token, &end_pos);
58 if (end_pos < token.length())
59 err("Invalid number %s", token.c_str());
63 if (*in_pos >= 'A' && *in_pos <= 'Z' ||
64 *in_pos >= 'a' && *in_pos <= 'z')
66 while (*in_pos >= 'A' && *in_pos <= 'Z' ||
67 *in_pos >= 'a' && *in_pos <= 'z' ||
68 *in_pos >= '0' && *in_pos <= '9' ||
78 while (*in_pos != '"')
81 err("Unterminated string");
85 if (*in_pos != '"' && *in_pos != '\\')
86 err("Unrecognized escape sequence \\%c", *in_pos);
104 return TOK_OPEN_PAREN;
106 return TOK_CLOSE_PAREN;
108 return TOK_OPEN_BRACE;
110 return TOK_CLOSE_BRACE;
112 if (c == '.' && *in_pos == '.')
117 err("Unrecognized character '%c'", c);
121 static token_type next_token()
123 if (buffered_token != TOK_NONE)
124 this_token = buffered_token;
126 this_token = get_next_token();
127 buffered_token = TOK_NONE;
131 static void return_token()
133 assert(this_token != TOK_NONE);
134 assert(buffered_token == TOK_NONE);
135 buffered_token = this_token;
136 this_token = TOK_NONE;
139 static token_type peek_token()
143 return buffered_token;
146 static bool token_is_int()
148 return token_num == (double)(int) token_num;
151 /*** Argument types ***/
153 class arg_int : public arg_val {
156 arg_int(int x) { val = x; }
157 bool given() { return true; }
158 int as_int(int def UNUSED) { return val; }
159 string dump() { return "<int> " + to_string(val); }
162 class arg_double : public arg_val {
165 arg_double(double x) { val = x; }
166 bool given() { return true; }
167 double as_double(double def UNUSED) { return val; }
168 string dump() { return "<double> " + to_string(val); }
171 class arg_string : public arg_val {
174 arg_string(string x) { val = x; }
175 bool given() { return true; }
176 const string as_string(string def UNUSED) { return val; }
177 string dump() { return "<string> \"" + val + '"'; }
191 static const unit units[] = {
201 static double parse_dimen(const arg_def *adef)
203 token_type t = next_token();
205 err("Argument %s must be a dimension", adef->name);
206 double tmp = token_num;
216 err("Argument %s must have a unit", adef->name);
218 for (uint i=0; units[i].name; i++)
219 if (token == units[i].name)
220 return tmp * units[i].multiplier;
221 err("Unknown unit %s", token.c_str());
224 static void parse_pipeline(cmd *c)
226 pipeline *pp = new pipeline;
229 while (peek_token() != TOK_CLOSE_BRACE)
231 if (pp->branches.size() && next_token() != TOK_COMMA)
232 err("Comma expected between pipeline branches");
234 pipeline_branch *pb = new pipeline_branch;
235 pp->branches.push_back(pb);
242 err("Missing close brace");
243 if (t == TOK_CLOSE_BRACE || t == TOK_COLON || t == TOK_COMMA)
246 pipeline_selector ps;
248 err("Pipeline selectors must start with a number");
250 err("Pipeline selectors must be integers");
251 ps.from = (int) token_num;
254 if (peek_token() == TOK_DOTDOT)
259 err("Pipeline selectors must be numbers or ranges");
261 err("Pipeline selectors must be integers");
262 ps.to = (int) token_num;
265 pb->selectors.push_back(ps);
269 parse_commands(pb->commands);
278 static void parse_args(cmd *c)
280 const cmd_def *cdef = c->def;
281 const arg_def *adefs = cdef->arg_defs;
283 while (adefs[num_args].name)
286 token_type t = next_token();
287 if (t != TOK_OPEN_PAREN)
293 bool saw_named = false;
298 if (t == TOK_CLOSE_PAREN)
301 while (next_pos < num_args && !(adefs[next_pos].type & AT_POSITIONAL))
305 bool has_value = false;
309 while (adefs[argi].name && token != adefs[argi].name)
311 if (adefs[argi].name)
313 if (c->args.count(token))
314 err("Argument %s given multiple times", token.c_str());
322 else if (next_pos < num_args && (adefs[next_pos].type & AT_TYPE_MASK) == AT_STRING)
324 // Shortcut syntax: positional arguments of string type can be specified
325 // as bare identifiers if they do not collide with names or other arguments.
331 err("Command %s has no argument %s", cdef->name, token.c_str());
334 err("Positional arguments must precede named ones");
335 else if (next_pos < num_args)
342 err("Too many positional arguments for command %s", cdef->name);
344 const arg_def *adef = &adefs[argi];
345 uint type = adef->type & AT_TYPE_MASK;
353 if (t != TOK_STRING && t != TOK_IDENT)
354 err("Argument %s must be a string or identifier", adef->name);
355 val = new arg_string(token);
359 if (t != TOK_NUMBER || !token_is_int())
360 err("Argument %s must be an integer", adef->name);
361 val = new arg_int((int) token_num);
366 err("Argument %s must be a number", adef->name);
367 val = new arg_double(token_num);
370 val = new arg_double(parse_dimen(adef));
374 if (t != TOK_NUMBER || !token_is_int() || ((int) token_num != 0 && (int) token_num != 1))
375 err("Argument %s must be a switch", adef->name);
376 val = new arg_int((int) token_num);
384 if (type == AT_SWITCH)
385 val = new arg_int(1);
387 err("Argument %s must have a value", adef->name);
390 c->args[adef->name] = val;
393 if (t == TOK_CLOSE_PAREN)
396 err("Comma expected after argument %s", adef->name);
399 for (uint i=0; i<num_args; i++)
400 if ((adefs[i].type & AT_MANDATORY) && !c->args.count(adefs[i].name))
401 err("Command %s is missing an argument %s", cdef->name, adefs[i].name);
404 static void debug_cmd(cmd *c, uint indent=0)
406 printf("%*sCommand %s\n", indent, "", c->def->name);
407 for (uint i=0; c->def->arg_defs[i].name; i++)
409 const arg_def *adef = &c->def->arg_defs[i];
410 string dump = c->arg(adef->name)->dump();
411 printf("%*sArg #%d: %s = %s\n", indent+4, "", (int) i, adef->name, dump.c_str());
415 printf("%*sPipeline:\n", indent+4, "");
416 for (auto pb: c->pipe->branches)
418 printf("%*sSelector:\n", indent+8, "");
419 for (auto ps: pb->selectors)
420 printf("%*s%d - %d\n", indent+12, "", ps.from, ps.to);
421 printf("%*sCommands:\n", indent+8, "");
422 for (auto cc: pb->commands)
423 debug_cmd(cc, indent+12);
428 static void debug_cmds(list<cmd *> &cmds)
434 static cmd *parse_cmd()
436 const cmd_def *cdef = cmd_table;
437 while (cdef->name && token != cdef->name)
440 err("Unknown command %s", token.c_str());
448 if (peek_token() == TOK_OPEN_BRACE)
450 if (!cdef->has_pipeline)
451 err("Command %s does not accept a pipeline", cdef->name);
454 else if (cdef->has_pipeline)
455 err("Command %s requires a pipeline", cdef->name);
460 static void parse_commands(list<cmd *> &cmds)
464 token_type t = next_token();
471 cmd *c = parse_cmd();
476 static void instantiate(list<cmd *> &cmds)
482 c->exec = c->def->constructor(c);
486 die("Error in %s: %s", c->def->name, e.what());
490 for (auto pb: c->pipe->branches)
491 instantiate(pb->commands);
496 void parse(const char *in, list<cmd *> &cmds)
498 debug("### Parsing commands");
503 parse_commands(cmds);
504 if (next_token() != TOK_END)
505 err("Extra tokens after commands");
509 die("Parse error: %s (at position %d)", e.what(), (int)(in_pos - in));
519 for (int i=0; cmd_table[i].name; i++)
521 const cmd_def *def = &cmd_table[i];
522 printf("%s - %s\n", def->name, def->help);
524 const arg_def *arg = def->arg_defs;
525 static const char * const type_names[] = {
526 "string", "int", "real", "dimen", "switch"
531 snprintf(a, sizeof(a), "%s%s=%s<%s>",
532 (arg->type & AT_POSITIONAL) ? "[" : "",
534 (arg->type & AT_POSITIONAL) ? "]" : "",
535 type_names[arg->type & AT_TYPE_MASK]);
536 printf(" %-20s %s\n", a, arg->help);
540 if (def->has_pipeline)
541 printf(" { pipeline }\n");