]> mj.ucw.cz Git - paperjam.git/blobdiff - parse.cc
Better error messages from instantiating and running of commands
[paperjam.git] / parse.cc
index 576795d3466c436572f07e834cc6aacc102cf831..8e7de38b3aec6a52f57a3f72bb23a98158f3aee4 100644 (file)
--- a/parse.cc
+++ b/parse.cc
@@ -1,3 +1,9 @@
+/*
+ *     PaperJam -- Command parser
+ *
+ *     (c) 2018 Martin Mares <mj@ucw.cz>
+ */
+
 #include <cassert>
 #include <cstdarg>
 #include <cstdlib>
@@ -30,7 +36,7 @@ static string token;
 static double token_num;
 
 static void NONRET parse_error(const char *msg, ...);
-static void parse_commands(list<cmd *> *cmds);
+static void parse_commands(list<cmd *> &cmds);
 
 static void parse_error(const char *msg, ...)
 {
@@ -71,7 +77,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;
     }
@@ -154,25 +162,34 @@ static bool token_is_int()
 
 /*** Argument types ***/
 
+class arg_int : public arg_val {
+  int val;
+public:
+  arg_int(int x) { val = x; }
+  bool given() { return true; }
+  int as_int(int def UNUSED) { return val; }
+  string dump() { return "<int> " + to_string(val); }
+};
+
 class arg_double : public arg_val {
   double val;
 public:
-  bool given() { return true; }
-  explicit operator double () { return val; }
   arg_double(double x) { val = x; }
-  string dump() { return to_string(val); }
+  bool given() { return true; }
+  double as_double(double def UNUSED) { return val; }
+  string dump() { return "<double> " + to_string(val); }
 };
 
 class arg_string : public arg_val {
   string val;
 public:
-  bool given() { return true; }
-  explicit operator string () { return val; }
   arg_string(string x) { val = x; }
-  string dump() { return '"' + val + '"'; }
+  bool given() { return true; }
+  const string as_string(string def UNUSED) { return val; }
+  string dump() { return "<string> \"" + val + '"'; }
 };
 
-static arg_val null_arg;
+arg_val null_arg;
 
 /*** Parser ***/
 
@@ -202,7 +219,14 @@ static double parse_dimen(const arg_def *adef)
 
   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;
+       }
+      parse_error("Parameter %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;
@@ -222,23 +246,15 @@ static void parse_pipeline(cmd *c)
       pipeline_branch *pb = new pipeline_branch;
       pp->branches.push_back(pb);
 
+      token_type t;
       for (;;)
        {
-         token_type t = next_token();
-         if (t == TOK_CLOSE_BRACE || t == TOK_END)
-           parse_error("Premature end of pipeline");
-         if (t == TOK_COLON)
+         t = next_token();
+         if (t == TOK_END)
+           parse_error("Missing close brace");
+         if (t == TOK_CLOSE_BRACE || t == TOK_COLON || t == TOK_COMMA)
            break;
 
-         if (pb->selectors.size())
-           {
-             if (t != TOK_COMMA)
-               parse_error("Invalid pipeline selector");
-             t = next_token();
-             if (t == TOK_CLOSE_BRACE || t == TOK_END)
-               parse_error("Premature end of pipeline");
-           }
-
          pipeline_selector ps;
          if (t != TOK_NUMBER)
            parse_error("Pipeline selectors must start with a number");
@@ -261,7 +277,10 @@ static void parse_pipeline(cmd *c)
          pb->selectors.push_back(ps);
        }
 
-      parse_commands(&pb->commands);
+      if (t == TOK_COLON)
+       parse_commands(pb->commands);
+      else
+       return_token();
     }
 
   c->pipe = pp;
@@ -276,8 +295,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)
     {
@@ -290,18 +307,23 @@ static void parse_args(cmd *c)
   for (;;)
     {
       t = next_token();
+      if (t == TOK_CLOSE_PAREN)
+        break;
       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())
+         if (c->args.count(token))
            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 '='");
+         if (t == TOK_EQUAL)
+           has_value = true;
+         else
+           return_token();
          saw_named = true;
        }
       else if (saw_named)
@@ -314,32 +336,56 @@ static void parse_args(cmd *c)
          if (next_pos >= num_args)
            parse_error("Too many positional arguments for command %s", cdef->name);
          argi = next_pos++;
+         has_value = true;
        }
 
       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_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)
+               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;
+           case AT_SWITCH:
+             t = next_token();
+             if (t != TOK_NUMBER || !token_is_int() || ((int) token_num != 0 && (int) token_num != 1))
+               parse_error("Parameter %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
+           parse_error("Parameter %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)
@@ -349,17 +395,17 @@ static void parse_args(cmd *c)
     }
 
   for (uint i=0; i<num_args; i++)
-    if ((adefs[i].type & AT_MANDATORY) && !c->args.at(i)->given())
+    if ((adefs[i].type & AT_MANDATORY) && !c->args.count(adefs[i].name))
       parse_error("Command %s is missing a parameter %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)
@@ -377,9 +423,9 @@ static void debug_cmd(cmd *c, uint indent=0)
     }
 }
 
-static void debug_cmds(list<cmd *> *cmds)
+static void debug_cmds(list<cmd *> &cmds)
 {
-  for (auto c: *cmds)
+  for (auto c: cmds)
     debug_cmd(c);
 }
 
@@ -409,7 +455,7 @@ static cmd *parse_cmd()
   return c;
 }
 
-static void parse_commands(list<cmd *> *cmds)
+static void parse_commands(list<cmd *> &cmds)
 {
   for (;;)
     {
@@ -421,11 +467,31 @@ static void parse_commands(list<cmd *> *cmds)
        }
 
       cmd *c = parse_cmd();
-      cmds->push_back(c);
+      cmds.push_back(c);
     }
 }
 
-void parse(const char *in, list<cmd *> *cmds)
+static void instantiate(list<cmd *> &cmds)
+{
+  for (auto c: cmds)
+    {
+      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)
+           instantiate(pb->commands);
+       }
+    }
+}
+
+void parse(const char *in, list<cmd *> &cmds)
 {
   in_pos = in;
   parse_commands(cmds);
@@ -433,4 +499,31 @@ void parse(const char *in, list<cmd *> *cmds)
     parse_error("Extra tokens after commands");
 
   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\n", def->name);
+
+      const arg_def *arg = def->arg_defs;
+      static const char * const type_names[] = {
+       "string", "int", "double", "dimen", "switch"
+      };
+      while (arg->name)
+       {
+         printf("\t%s (%s)%s%s\n",
+           arg->name,
+           type_names[arg->type & AT_TYPE_MASK],
+           (arg->type & AT_MANDATORY) ? " [mandatory]" : "",
+           (arg->type & AT_POSITIONAL) ? " [positional]" : "");
+         arg++;
+       }
+
+       if (def->has_pipeline)
+         printf("\t{ pipeline }\n");
+    }
 }