docs: improve unformat documentation 26/21926/2
authorDave Barach <dave@barachs.net>
Mon, 9 Sep 2019 15:02:45 +0000 (11:02 -0400)
committerFlorin Coras <florin.coras@gmail.com>
Mon, 9 Sep 2019 18:37:35 +0000 (18:37 +0000)
Specifically: document the unformat specifiers "%=", "%|", and correct
usage of unformat_line_input.

Signed-off-by: Dave Barach <dave@barachs.net>
Change-Id: I254f9c4a61c5c8d3edbf76f521e5f38fb89150f1

docs/gettingstarted/developers/infrastructure.md

index 9dde982..95465f0 100644 (file)
@@ -49,7 +49,7 @@ macro! It's smart about NULL pointers.\]
 Typically, the user header is not present. User headers allow for other
 data structures to be built atop vppinfra vectors. Users may specify the
 alignment for first data element of a vector via the \[vec\]()\*\_aligned
-macros. 
+macros.
 
 Vector elements can be any C type e.g. (int, double, struct bar). This
 is also true for data types built atop vectors (e.g. heap, pool, etc.).
@@ -138,8 +138,8 @@ schemes may be used:
 
 ```c
     vec_add1 (result, 0)
-    or 
-    result = format (result, "<whatever>%c", 0); 
+    or
+    result = format (result, "<whatever>%c", 0);
 ```
 
 Remember to vec\_free() the result if appropriate. Be careful not to
@@ -161,8 +161,8 @@ format specification. For example:
 
 format\_junk() can invoke other user-format functions if desired. The
 programmer shoulders responsibility for argument type-checking. It is
-typical for user format functions to blow up if the va\_arg(va,
-type) macros don't match the caller's idea of reality.
+typical for user format functions to blow up spectaculary if the
+va\_arg(va, type) macros don't match the caller's idea of reality.
 
 Unformat
 --------
@@ -185,20 +185,132 @@ follows:
 Then loop parsing individual elements:
 
 ```c
-    while (unformat_check_input (&input) != UNFORMAT_END_OF_INPUT) 
+    while (unformat_check_input (&input) != UNFORMAT_END_OF_INPUT)
     {
       if (unformat (&input, "value1 %d", &value1))
         ;/* unformat sets value1 */
       else if (unformat (&input, "value2 %d", &value2)
         ;/* unformat sets value2 */
       else
-        return clib_error_return (0, "unknown input '%U'", 
+        return clib_error_return (0, "unknown input '%U'",
                                   format_unformat_error, input);
     }
 ```
 
 As with format, unformat implements a user-unformat function capability
-via a "%U" user unformat function scheme.
+via a "%U" user unformat function scheme. Generally, one can trivially
+transform "format (s, "foo %d", foo) -> "unformat (input, "foo %d", &foo)".
+
+Unformat implements a couple of handy non-scanf-like format specifiers:
+
+```c
+    unformat (input, "enable %=", &enable, 1 /* defaults to 1 */);
+    unformat (input, "bitzero %|", &mask, (1<<0));
+    unformat (input, "bitone %|", &mask, (1<<1));
+    <etc>
+```
+
+The phrase "enable %=" means "set the supplied variable to the default
+value" if unformat parses the "enable" keyword all by itself. If
+unformat parses "enable 123" set the supplied variable to 123.
+
+We could clean up a number of hand-rolled "verbose" + "verbose %d"
+argument parsing codes using "%=".
+
+The phrase "bitzero %|" means "set the specified bit in the supplied
+bitmask" if unformat parses "bitzero". Although it looks like it could
+be fairly handy, it's very lightly used in the code base.
+
+### How to parse a single input line
+
+Debug CLI command functions MUST NOT accidentally consume input
+belonging to other debug CLI commands. Otherwise, it's impossible to
+script a set of debug CLI commands which "work fine" when issued one
+at a time.
+
+This bit of code is NOT correct:
+
+```c
+  /* Eats script input NOT beloging to it, and chokes! */
+  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (input, ...))
+       ;
+      else if (unformat (input, ...))
+       ;
+      else
+        return clib_error_return (0, "parse error: '%U'",
+                                    format_unformat_error, input);
+       }
+    }
+```
+
+When executed as part of a script, such a function will return "parse
+error: '<next-command-text>'" every time, unless it happens to be the
+last command in the script.
+
+Instead, use "unformat_line_input" to consume the rest of a line's
+worth of input - everything past the path specified in the
+VLIB_CLI_COMMAND declaration.
+
+For example, unformat_line_input with "my_command" set up as shown
+below and user input "my path is clear" will produce an
+unformat_input_t that contains "is clear".
+
+```c
+    VLIB_CLI_COMMAND (...) = {
+        .path = "my path",
+    };
+```
+
+Here's a bit of code which shows the required mechanics, in full:
+
+```c
+    static clib_error_t *
+    my_command_fn (vlib_main_t * vm,
+                   unformat_input_t * input,
+                   vlib_cli_command_t * cmd)
+    {
+      unformat_input_t _line_input, *line_input = &_line_input;
+      u32 this, that;
+      clib_error_t *error = 0;
+
+      if (!unformat_user (input, unformat_line_input, line_input))
+        return 0;
+
+      /*
+       * Here, UNFORMAT_END_OF_INPUT is at the end of the line we consumed,
+       * not at the end of the script...
+       */
+      while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+        {
+           if (unformat (line_input, "this %u", &this))
+             ;
+           else if (unformat (line_input, "that %u", &that))
+             ;
+           else
+             {
+               error = clib_error_return (0, "parse error: '%U'",
+                                    format_unformat_error, line_input);
+               goto done;
+             }
+          }
+
+    <do something based on "this" and "that", etc>
+
+    done:
+      unformat_free (line_input);
+      return error;
+    }
+   /* *INDENT-OFF* */
+   VLIB_CLI_COMMAND (my_command, static) = {
+     .path = "my path",
+     .function = my_command_fn",
+   };
+   /* *INDENT-ON* */
+
+```
+
 
 Vppinfra errors and warnings
 ----------------------------