/ externals / zycore / src / ArgParse.c
ArgParse.c
  1  /***************************************************************************************************
  2  
  3    Zyan Core Library (Zycore-C)
  4  
  5    Original Author : Joel Hoener
  6  
  7   * Permission is hereby granted, free of charge, to any person obtaining a copy
  8   * of this software and associated documentation files (the "Software"), to deal
  9   * in the Software without restriction, including without limitation the rights
 10   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 11   * copies of the Software, and to permit persons to whom the Software is
 12   * furnished to do so, subject to the following conditions:
 13   *
 14   * The above copyright notice and this permission notice shall be included in all
 15   * copies or substantial portions of the Software.
 16   *
 17   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 18   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 19   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 20   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 21   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 22   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 23   * SOFTWARE.
 24  
 25  ***************************************************************************************************/
 26  
 27  #include <Zycore/ArgParse.h>
 28  #include <Zycore/LibC.h>
 29  
 30  /* ============================================================================================== */
 31  /* Exported functions                                                                             */
 32  /* ============================================================================================== */
 33  
 34  #ifndef ZYAN_NO_LIBC
 35  
 36  ZyanStatus ZyanArgParse(const ZyanArgParseConfig *cfg, ZyanVector* parsed,
 37      const char** error_token)
 38  {
 39      return ZyanArgParseEx(cfg, parsed, error_token, ZyanAllocatorDefault());
 40  }
 41  
 42  #endif
 43  
 44  ZyanStatus ZyanArgParseEx(const ZyanArgParseConfig *cfg, ZyanVector* parsed,
 45      const char** error_token, ZyanAllocator* allocator)
 46  {
 47  #   define ZYAN_ERR_TOK(tok) if (error_token) { *error_token = tok; }
 48  
 49      ZYAN_ASSERT(cfg);
 50      ZYAN_ASSERT(parsed);
 51  
 52      // TODO: Once we have a decent hash map impl, refactor this to use it. The majority of for
 53      //       loops through the argument list could be avoided.
 54  
 55      if (cfg->min_unnamed_args > cfg->max_unnamed_args)
 56      {
 57          return ZYAN_STATUS_INVALID_ARGUMENT;
 58      }
 59  
 60      // Check argument syntax.
 61      for (const ZyanArgParseDefinition* def = cfg->args; def && def->name; ++def)
 62      {
 63          // TODO: Duplicate check
 64  
 65          if (!def->name)
 66          {
 67              return ZYAN_STATUS_INVALID_ARGUMENT;
 68          }
 69  
 70          ZyanUSize arg_len = ZYAN_STRLEN(def->name);
 71          if (arg_len < 2 || def->name[0] != '-')
 72          {
 73              return ZYAN_STATUS_INVALID_ARGUMENT;
 74          }
 75  
 76          // Single dash arguments only accept a single char name.
 77          if (def->name[1] != '-' && arg_len != 2)
 78          {
 79              return ZYAN_STATUS_INVALID_ARGUMENT;
 80          }
 81      }
 82  
 83      // Initialize output vector.
 84      ZYAN_CHECK(ZyanVectorInitEx(parsed, sizeof(ZyanArgParseArg), cfg->argc, ZYAN_NULL, allocator,
 85          ZYAN_VECTOR_DEFAULT_GROWTH_FACTOR, ZYAN_VECTOR_DEFAULT_SHRINK_THRESHOLD));
 86  
 87      ZyanStatus err;
 88      ZyanBool accept_dash_args = ZYAN_TRUE;
 89      ZyanUSize num_unnamed_args = 0;
 90      for (ZyanUSize i = 1; i < cfg->argc; ++i)
 91      {
 92          const char* cur_arg = cfg->argv[i];
 93          ZyanUSize arg_len = ZYAN_STRLEN(cfg->argv[i]);
 94  
 95          // Double-dash argument?
 96          if (accept_dash_args && arg_len >= 2 && ZYAN_MEMCMP(cur_arg, "--", 2) == 0)
 97          {
 98              // GNU style end of argument parsing.
 99              if (arg_len == 2)
100              {
101                  accept_dash_args = ZYAN_FALSE;
102              }
103              // Regular double-dash argument.
104              else
105              {
106                  // Allocate parsed argument struct.
107                  ZyanArgParseArg* parsed_arg;
108                  ZYAN_CHECK(ZyanVectorEmplace(parsed, (void**)&parsed_arg, ZYAN_NULL));
109                  ZYAN_MEMSET(parsed_arg, 0, sizeof(*parsed_arg));
110  
111                  // Find corresponding argument definition.
112                  for (const ZyanArgParseDefinition* def = cfg->args; def && def->name; ++def)
113                  {
114                      if (ZYAN_STRCMP(def->name, cur_arg) == 0)
115                      {
116                          parsed_arg->def = def;
117                          break;
118                      }
119                  }
120  
121                  // Search exhausted & argument not found. RIP.
122                  if (!parsed_arg->def)
123                  {
124                      err = ZYAN_STATUS_ARG_NOT_UNDERSTOOD;
125                      ZYAN_ERR_TOK(cur_arg);
126                      goto failure;
127                  }
128  
129                  // Does the argument expect a value? If yes, consume next token.
130                  if (!parsed_arg->def->boolean)
131                  {
132                      if (i == cfg->argc - 1)
133                      {
134                          err = ZYAN_STATUS_ARG_MISSES_VALUE;
135                          ZYAN_ERR_TOK(cur_arg);
136                          goto failure;
137                      }
138                      parsed_arg->has_value = ZYAN_TRUE;
139                      ZYAN_CHECK(ZyanStringViewInsideBuffer(&parsed_arg->value, cfg->argv[++i]));
140                  }
141              }
142  
143              // Continue parsing at next token.
144              continue;
145          }
146  
147          // Single-dash argument?
148          // TODO: How to deal with just dashes? Current code treats it as unnamed arg.
149          if (accept_dash_args && arg_len > 1 && cur_arg[0] == '-')
150          {
151              // Iterate argument token chars until there are either no more chars left
152              // or we encounter a non-boolean argument, in which case we consume the
153              // remaining chars as its value.
154              for (const char* read_ptr = cur_arg + 1; *read_ptr; ++read_ptr)
155              {
156                  // Allocate parsed argument struct.
157                  ZyanArgParseArg* parsed_arg;
158                  ZYAN_CHECK(ZyanVectorEmplace(parsed, (void**)&parsed_arg, ZYAN_NULL));
159                  ZYAN_MEMSET(parsed_arg, 0, sizeof(*parsed_arg));
160  
161                  // Find corresponding argument definition.
162                  for (const ZyanArgParseDefinition* def = cfg->args; def && def->name; ++def)
163                  {
164                      if (ZYAN_STRLEN(def->name) == 2 &&
165                          def->name[0] == '-' &&
166                          def->name[1] == *read_ptr)
167                      {
168                          parsed_arg->def = def;
169                          break;
170                      }
171                  }
172  
173                  // Search exhausted, no match found?
174                  if (!parsed_arg->def)
175                  {
176                      err = ZYAN_STATUS_ARG_NOT_UNDERSTOOD;
177                      ZYAN_ERR_TOK(cur_arg);
178                      goto failure;
179                  }
180  
181                  // Requires value?
182                  if (!parsed_arg->def->boolean)
183                  {
184                      // If there are chars left, consume them (e.g. `-n1000`).
185                      if (read_ptr[1])
186                      {
187                          parsed_arg->has_value = ZYAN_TRUE;
188                          ZYAN_CHECK(ZyanStringViewInsideBuffer(&parsed_arg->value, read_ptr + 1));
189                      }
190                      // If not, consume next token (e.g. `-n 1000`).
191                      else
192                      {
193                          if (i == cfg->argc - 1)
194                          {
195                              err = ZYAN_STATUS_ARG_MISSES_VALUE;
196                              ZYAN_ERR_TOK(cur_arg)
197                              goto failure;
198                          }
199  
200                          parsed_arg->has_value = ZYAN_TRUE;
201                          ZYAN_CHECK(ZyanStringViewInsideBuffer(&parsed_arg->value, cfg->argv[++i]));
202                      }
203  
204                      // Either way, continue with next argument.
205                      goto continue_main_loop;
206                  }
207              }
208          }
209  
210          // Still here? We're looking at an unnamed argument.
211          ++num_unnamed_args;
212          if (num_unnamed_args > cfg->max_unnamed_args)
213          {
214              err = ZYAN_STATUS_TOO_MANY_ARGS;
215              ZYAN_ERR_TOK(cur_arg);
216              goto failure;
217          }
218  
219          // Allocate parsed argument struct.
220          ZyanArgParseArg* parsed_arg;
221          ZYAN_CHECK(ZyanVectorEmplace(parsed, (void**)&parsed_arg, ZYAN_NULL));
222          ZYAN_MEMSET(parsed_arg, 0, sizeof(*parsed_arg));
223          parsed_arg->has_value = ZYAN_TRUE;
224          ZYAN_CHECK(ZyanStringViewInsideBuffer(&parsed_arg->value, cur_arg));
225  
226      continue_main_loop:;
227      }
228  
229      // All tokens processed. Do we have enough unnamed arguments?
230      if (num_unnamed_args < cfg->min_unnamed_args)
231      {
232          err = ZYAN_STATUS_TOO_FEW_ARGS;
233          // No sensible error token for this error type.
234          goto failure;
235      }
236  
237      // Check whether all required arguments are present.
238      ZyanUSize num_parsed_args;
239      ZYAN_CHECK(ZyanVectorGetSize(parsed, &num_parsed_args));
240      for (const ZyanArgParseDefinition* def = cfg->args; def && def->name; ++def)
241      {
242          if (!def->required) continue;
243  
244          ZyanBool arg_found = ZYAN_FALSE;
245          for (ZyanUSize i = 0; i < num_parsed_args; ++i)
246          {
247              const ZyanArgParseArg* arg = ZYAN_NULL;
248              ZYAN_CHECK(ZyanVectorGetPointer(parsed, i, (const void**)&arg));
249  
250              // Skip unnamed args.
251              if (!arg->def) continue;
252  
253              if (arg->def == def)
254              {
255                  arg_found = ZYAN_TRUE;
256                  break;
257              }
258          }
259  
260          if (!arg_found)
261          {
262              err = ZYAN_STATUS_REQUIRED_ARG_MISSING;
263              ZYAN_ERR_TOK(def->name);
264              goto failure;
265          }
266      }
267  
268      // Yay!
269      ZYAN_ERR_TOK(ZYAN_NULL);
270      return ZYAN_STATUS_SUCCESS;
271  
272  failure:
273      ZYAN_CHECK(ZyanVectorDestroy(parsed));
274      return err;
275  
276  #   undef ZYAN_ERR_TOK
277  }
278  
279  /* ============================================================================================== */