X-Git-Url: http://mj.ucw.cz/gitweb/?a=blobdiff_plain;f=parse.cc;h=d1573c88e84561bca9540231218f2dc441e5440a;hb=HEAD;hp=2e38d3ad403204451ea2ace07e6a8d3079c78b57;hpb=0527588aa3555c88b096f0ad2ab6147c3ec395d6;p=paperjam.git diff --git a/parse.cc b/parse.cc index 2e38d3a..d1573c8 100644 --- a/parse.cc +++ b/parse.cc @@ -1,3 +1,9 @@ +/* + * PaperJam -- Command parser + * + * (c) 2018 Martin Mares + */ + #include #include #include @@ -29,20 +35,8 @@ static token_type buffered_token = TOK_NONE; static string token; static double token_num; -static void NONRET parse_error(const char *msg, ...); static void parse_commands(list &cmds); -static void parse_error(const char *msg, ...) -{ - va_list args; - va_start(args, msg); - fprintf(stderr, "Parse error: "); - vfprintf(stderr, msg, args); - fprintf(stderr, "\n"); - va_end(args); - exit(1); -} - static token_type get_next_token() { while (*in_pos == ' ' || *in_pos == '\t' || *in_pos == '\r' || *in_pos == '\n') @@ -62,7 +56,7 @@ static token_type get_next_token() size_t end_pos; token_num = stod(token, &end_pos); if (end_pos < token.length()) - parse_error("Invalid number %s", token.c_str()); + err("Invalid number %s", token.c_str()); return TOK_NUMBER; } @@ -71,7 +65,9 @@ static token_type get_next_token() { while (*in_pos >= 'A' && *in_pos <= 'Z' || *in_pos >= 'a' && *in_pos <= 'z' || - *in_pos >= '0' && *in_pos <= '9') + *in_pos >= '0' && *in_pos <= '9' || + *in_pos == '_' || + *in_pos == '-') token += *in_pos++; return TOK_IDENT; } @@ -82,12 +78,12 @@ static token_type get_next_token() while (*in_pos != '"') { if (!*in_pos) - parse_error("Unterminated string"); + err("Unterminated string"); if (*in_pos == '\\') { in_pos++; if (*in_pos != '"' && *in_pos != '\\') - parse_error("Unrecognized escape sequence \\%c", *in_pos); + err("Unrecognized escape sequence \\%c", *in_pos); } token += *in_pos++; } @@ -118,7 +114,7 @@ static token_type get_next_token() in_pos++; return TOK_DOTDOT; } - parse_error("Unrecognized character '%c'", c); + err("Unrecognized character '%c'", c); } } @@ -181,7 +177,7 @@ public: string dump() { return " \"" + val + '"'; } }; -static arg_val null_arg; +arg_val null_arg; /*** Parser ***/ @@ -206,16 +202,23 @@ static double parse_dimen(const arg_def *adef) { token_type t = next_token(); if (t != TOK_NUMBER) - parse_error("Parameter %s must be a dimension", adef->name); + err("Argument %s must be a dimension", adef->name); double tmp = token_num; t = next_token(); if (t != TOK_IDENT) - parse_error("Parameter %s must have a unit", adef->name); + { + if (is_zero(tmp)) + { + return_token(); + return 0; + } + err("Argument %s must have a unit", adef->name); + } for (uint i=0; units[i].name; i++) if (token == units[i].name) return tmp * units[i].multiplier; - parse_error("Unknown unit %s", token.c_str()); + err("Unknown unit %s", token.c_str()); } static void parse_pipeline(cmd *c) @@ -226,7 +229,7 @@ static void parse_pipeline(cmd *c) while (peek_token() != TOK_CLOSE_BRACE) { if (pp->branches.size() && next_token() != TOK_COMMA) - parse_error("Comma expected between pipeline branches"); + err("Comma expected between pipeline branches"); pipeline_branch *pb = new pipeline_branch; pp->branches.push_back(pb); @@ -236,15 +239,15 @@ static void parse_pipeline(cmd *c) { t = next_token(); if (t == TOK_END) - parse_error("Missing close brace"); + err("Missing close brace"); if (t == TOK_CLOSE_BRACE || t == TOK_COLON || t == TOK_COMMA) break; pipeline_selector ps; if (t != TOK_NUMBER) - parse_error("Pipeline selectors must start with a number"); + err("Pipeline selectors must start with a number"); if (!token_is_int()) - parse_error("Pipeline selectors must be integers"); + err("Pipeline selectors must be integers"); ps.from = (int) token_num; ps.to = ps.from; @@ -253,9 +256,9 @@ static void parse_pipeline(cmd *c) next_token(); t = next_token(); if (t != TOK_NUMBER) - parse_error("Pipeline selectors must be numbers or ranges"); + err("Pipeline selectors must be numbers or ranges"); if (!token_is_int()) - parse_error("Pipeline selectors must be integers"); + err("Pipeline selectors must be integers"); ps.to = (int) token_num; } @@ -280,8 +283,6 @@ static void parse_args(cmd *c) while (adefs[num_args].name) num_args++; - c->args.resize(num_args, &null_arg); - token_type t = next_token(); if (t != TOK_OPEN_PAREN) { @@ -294,82 +295,119 @@ static void parse_args(cmd *c) for (;;) { t = next_token(); + if (t == TOK_CLOSE_PAREN) + break; + + while (next_pos < num_args && !(adefs[next_pos].type & AT_POSITIONAL)) + next_pos++; + uint argi = 0; + bool has_value = false; + if (t == TOK_IDENT) { while (adefs[argi].name && token != adefs[argi].name) argi++; - if (!adefs[argi].name) - parse_error("Command %s has no parameter %s", cdef->name, token.c_str()); - if (c->args.at(argi)->given()) - parse_error("Parameter %s given multiple times", token.c_str()); - t = next_token(); - if (t != TOK_EQUAL) - parse_error("Parameter name must be followed by '='"); - saw_named = true; + if (adefs[argi].name) + { + if (c->args.count(token)) + err("Argument %s given multiple times", token.c_str()); + t = next_token(); + if (t == TOK_EQUAL) + has_value = true; + else + return_token(); + saw_named = true; + } + else if (next_pos < num_args && (adefs[next_pos].type & AT_TYPE_MASK) == AT_STRING) + { + // Shortcut syntax: positional arguments of string type can be specified + // as bare identifiers if they do not collide with names or other arguments. + return_token(); + argi = next_pos++; + has_value = true; + } + else + err("Command %s has no argument %s", cdef->name, token.c_str()); } else if (saw_named) - parse_error("Positional parameters must precede named ones"); - else + err("Positional arguments must precede named ones"); + else if (next_pos < num_args) { return_token(); - while (next_pos < num_args && !(adefs[next_pos].type & AT_POSITIONAL)) - next_pos++; - if (next_pos >= num_args) - parse_error("Too many positional arguments for command %s", cdef->name); argi = next_pos++; + has_value = true; } + else + err("Too many positional arguments for command %s", cdef->name); const arg_def *adef = &adefs[argi]; + uint type = adef->type & AT_TYPE_MASK; arg_val *val = NULL; - switch (adef->type & AT_TYPE_MASK) - { - case AT_STRING: - t = next_token(); - if (t != TOK_STRING) - parse_error("Parameter %s must be a string", adef->name); - val = new arg_string(token); - break; - case AT_INT: - t = next_token(); - if (t != TOK_NUMBER || !token_is_int()) - parse_error("Parameter %s must be an integer", adef->name); - val = new arg_int((int) token_num); - break; - case AT_DOUBLE: - t = next_token(); - if (t != TOK_NUMBER) - parse_error("Parameter %s must be a number", adef->name); - val = new arg_double(token_num); - break; - case AT_DIMEN: - val = new arg_double(parse_dimen(adef)); - break; - default: - abort(); + if (has_value) + { + switch (type) + { + case AT_STRING: + t = next_token(); + if (t != TOK_STRING && t != TOK_IDENT) + err("Argument %s must be a string or identifier", adef->name); + val = new arg_string(token); + break; + case AT_INT: + t = next_token(); + if (t != TOK_NUMBER || !token_is_int()) + err("Argument %s must be an integer", adef->name); + val = new arg_int((int) token_num); + break; + case AT_DOUBLE: + t = next_token(); + if (t != TOK_NUMBER) + err("Argument %s must be a number", adef->name); + val = new arg_double(token_num); + break; + case AT_DIMEN: + val = new arg_double(parse_dimen(adef)); + break; + case AT_SWITCH: + t = next_token(); + if (t != TOK_NUMBER || !token_is_int() || ((int) token_num != 0 && (int) token_num != 1)) + err("Argument %s must be a switch", adef->name); + val = new arg_int((int) token_num); + break; + default: + abort(); + } + } + else + { + if (type == AT_SWITCH) + val = new arg_int(1); + else + err("Argument %s must have a value", adef->name); } - c->args.at(argi) = val; + c->args[adef->name] = val; t = next_token(); if (t == TOK_CLOSE_PAREN) break; if (t != TOK_COMMA) - parse_error("Comma expected after parameter %s", adef->name); + err("Comma expected after argument %s", adef->name); } for (uint i=0; iargs.at(i)->given()) - parse_error("Command %s is missing a parameter %s", cdef->name, adefs[i].name); + if ((adefs[i].type & AT_MANDATORY) && !c->args.count(adefs[i].name)) + err("Command %s is missing an argument %s", cdef->name, adefs[i].name); } static void debug_cmd(cmd *c, uint indent=0) { printf("%*sCommand %s\n", indent, "", c->def->name); - for (size_t i=0; i < c->args.size(); i++) + for (uint i=0; c->def->arg_defs[i].name; i++) { const arg_def *adef = &c->def->arg_defs[i]; - string dump = c->args.at(i)->dump(); + string dump = c->arg(adef->name)->dump(); printf("%*sArg #%d: %s = %s\n", indent+4, "", (int) i, adef->name, dump.c_str()); } if (c->pipe) @@ -399,7 +437,7 @@ static cmd *parse_cmd() while (cdef->name && token != cdef->name) cdef++; if (!cdef->name) - parse_error("Unknown command %s", token.c_str()); + err("Unknown command %s", token.c_str()); cmd *c = new cmd; c->def = cdef; @@ -410,11 +448,11 @@ static cmd *parse_cmd() if (peek_token() == TOK_OPEN_BRACE) { if (!cdef->has_pipeline) - parse_error("Command %s does not accept a pipeline", cdef->name); + err("Command %s does not accept a pipeline", cdef->name); parse_pipeline(c); } else if (cdef->has_pipeline) - parse_error("Command %s requires a pipeline", cdef->name); + err("Command %s requires a pipeline", cdef->name); return c; } @@ -439,7 +477,14 @@ static void instantiate(list &cmds) { for (auto c: cmds) { - c->exec = c->def->constructor(c); + try + { + c->exec = c->def->constructor(c); + } + catch (exception &e) + { + die("Error in %s: %s", c->def->name, e.what()); + } if (c->pipe) { for (auto pb: c->pipe->branches) @@ -450,11 +495,49 @@ static void instantiate(list &cmds) void parse(const char *in, list &cmds) { + debug("### Parsing commands"); in_pos = in; - parse_commands(cmds); - if (next_token() != TOK_END) - parse_error("Extra tokens after commands"); - debug_cmds(cmds); + try + { + parse_commands(cmds); + if (next_token() != TOK_END) + err("Extra tokens after commands"); + } + catch (exception &e) + { + die("Parse error: %s (at position %d)", e.what(), (int)(in_pos - in)); + } + + if (debug_level > 1) + debug_cmds(cmds); instantiate(cmds); } + +void parser_help() +{ + for (int i=0; cmd_table[i].name; i++) + { + const cmd_def *def = &cmd_table[i]; + printf("%s - %s\n", def->name, def->help); + + const arg_def *arg = def->arg_defs; + static const char * const type_names[] = { + "string", "int", "real", "dimen", "switch" + }; + while (arg->name) + { + char a[32]; + snprintf(a, sizeof(a), "%s%s=%s<%s>", + (arg->type & AT_POSITIONAL) ? "[" : "", + arg->name, + (arg->type & AT_POSITIONAL) ? "]" : "", + type_names[arg->type & AT_TYPE_MASK]); + printf(" %-20s %s\n", a, arg->help); + arg++; + } + + if (def->has_pipeline) + printf(" { pipeline }\n"); + } +}