text: /* Splitting string into tokens, taking quotes and escape sequences into account.
   * From https://github.com/espressif/esp-idf/blob/master/components/console/split_argv.c
   * Copyright 2016-2017 Espressif Systems (Shanghai) PTE LTD
   * Licensed under the Apache License 2.0.
   */
  
  #ifndef BS_ARGS_H
  #define BS_ARGS_H
  
  #include <stdio.h>
  #include <ctype.h>
  #include <string.h>
  
  namespace bs
  {
  namespace protocol
  {
  
  #define SS_FLAG_ESCAPE 0x8
  
  typedef enum {
      /* parsing the space between arguments */
      SS_SPACE = 0x0,
      /* parsing an argument which isn't quoted */
      SS_ARG = 0x1,
      /* parsing a quoted argument */
      SS_QUOTED_ARG = 0x2,
      /* parsing an escape sequence within unquoted argument */
      SS_ARG_ESCAPED = SS_ARG | SS_FLAG_ESCAPE,
      /* parsing an escape sequence within a quoted argument */
      SS_QUOTED_ARG_ESCAPED = SS_QUOTED_ARG | SS_FLAG_ESCAPE,
  } split_state_t;
  
  /* helper macro, called when done with an argument */
  #define END_ARG() do { \
          char_out = 0;   \
          argv[argc++] = next_arg_start;  \
          state = SS_SPACE;   \
      } while(0);
  
  
  /**
   * @brief Split command line into arguments in place
   *
   * - This function finds whitespace-separated arguments in the given input line.
   *
   *     'abc def 1 20 .3' -> [ 'abc', 'def', '1', '20', '.3' ]
   *
   * - Argument which include spaces may be surrounded with quotes. In this case
   *   spaces are preserved and quotes are stripped.
   *
   *     'abc "123 456" def' -> [ 'abc', '123 456', 'def' ]
   *
   * - Escape sequences may be used to produce backslash, double quote, and space:
   *
   *     'a\ b\\c\"' -> [ 'a b\c"' ]
   *
   * Pointers to at most argv_size - 1 arguments are returned in argv array.
   * The pointer after the last one (i.e. argv[argc]) is set to NULL.
   *
   * @param line pointer to buffer to parse; it is modified in place
   * @param argv array where the pointers to arguments are written
   * @param argv_size number of elements in argv_array (max. number of arguments will be argv_size - 1)
   * @return number of arguments found (argc)
   */
  inline size_t split_args(char *line, char **argv, size_t argv_size)
  {
      const int QUOTE = '"';
      const int ESCAPE = '\\';
      const int SPACE = ' ';
      split_state_t state = SS_SPACE;
      size_t argc = 0;
      char *next_arg_start = line;
      char *out_ptr = line;
      for (char *in_ptr = line; argc < argv_size - 1; ++in_ptr) {
          int char_in = (unsigned char) *in_ptr;
          if (char_in == 0) {
              break;
          }
          int char_out = -1;
  
          switch (state) {
          case SS_SPACE:
              if (char_in == SPACE) {
                  /* skip space */
              } else if (char_in == QUOTE) {
                  next_arg_start = out_ptr;
                  state = SS_QUOTED_ARG;
              } else if (char_in == ESCAPE) {
                  next_arg_start = out_ptr;
                  state = SS_ARG_ESCAPED;
              } else {
                  next_arg_start = out_ptr;
                  state = SS_ARG;
                  char_out = char_in;
              }
              break;
  
          case SS_QUOTED_ARG:
              if (char_in == QUOTE) {
                  END_ARG();
              } else if (char_in == ESCAPE) {
                  state = SS_QUOTED_ARG_ESCAPED;
              } else {
                  char_out = char_in;
              }
              break;
  
          case SS_ARG_ESCAPED:
          case SS_QUOTED_ARG_ESCAPED:
              if (char_in == ESCAPE || char_in == QUOTE || char_in == SPACE) {
                  char_out = char_in;
              } else {
                  /* unrecognized escape character, skip */
              }
              state = (split_state_t) (state & (~SS_FLAG_ESCAPE));
              break;
  
          case SS_ARG:
              if (char_in == SPACE) {
                  END_ARG();
              } else if (char_in == ESCAPE) {
                  state = SS_ARG_ESCAPED;
              } else {
                  char_out = char_in;
              }
              break;
          }
          /* need to output anything? */
          if (char_out >= 0) {
              *out_ptr = char_out;
              ++out_ptr;
          }
      }
      /* make sure the final argument is terminated */
      *out_ptr = 0;
      /* finalize the last argument */
      if (state != SS_SPACE && argc < argv_size - 1) {
          argv[argc++] = next_arg_start;
      }
      /* add a NULL at the end of argv */
      argv[argc] = NULL;
  
      return argc;
  }
  
  } // namespace bs
  
  } // namespace protocol
  
  #endif //BS_ARGS_H
  