text: /* -*- Mode: C; c-basic-offset:4 ; indent-tabs-mode:nil -*- */
  /*
   * Copyright (c) 2004-2005 The Trustees of Indiana University and Indiana
   *                         University Research and Technology
   *                         Corporation.  All rights reserved.
   * Copyright (c) 2004-2005 The University of Tennessee and The University
   *                         of Tennessee Research Foundation.  All rights
   *                         reserved.
   * Copyright (c) 2004-2005 High Performance Computing Center Stuttgart,
   *                         University of Stuttgart.  All rights reserved.
   * Copyright (c) 2004-2005 The Regents of the University of California.
   *                         All rights reserved.
   * Copyright (c) 2015-2016 Los Alamos National Security, LLC. All rights
   *                         reserved.
   * Copyright (c) 2016-2020 Intel, Inc.  All rights reserved.
   * Copyright (c) 2021      Nanook Consulting.  All rights reserved.
   * $COPYRIGHT$
   *
   * Additional copyrights may follow
   *
   * $HEADER$
   */
  
  #include "src/include/pmix_config.h"
  
  #include "include/pmix_common.h"
  #include "src/util/keyval/keyval_lex.h"
  #include "src/util/keyval_parse.h"
  #include "src/util/output.h"
  #include <ctype.h>
  #include <string.h>
  
  int pmix_util_keyval_parse_lineno = 0;
  
  static const char *keyval_filename;
  static pmix_keyval_parse_fn_t keyval_callback;
  static char *key_buffer = NULL;
  static size_t key_buffer_len = 0;
  
  static int parse_line(void);
  static int parse_line_new(pmix_keyval_parse_state_t first_val);
  static void parse_error(int num);
  
  static char *env_str = NULL;
  static int envsize = 1024;
  
  int pmix_util_keyval_parse_init(void)
  {
      return PMIX_SUCCESS;
  }
  
  int pmix_util_keyval_parse_finalize(void)
  {
      if (NULL != key_buffer)
          free(key_buffer);
      key_buffer = NULL;
      key_buffer_len = 0;
  
      return PMIX_SUCCESS;
  }
  
  int pmix_util_keyval_parse(const char *filename, pmix_keyval_parse_fn_t callback)
  {
      pmix_keyval_parse_state_t val;
      int ret = PMIX_SUCCESS;
      ;
  
      keyval_filename = filename;
      keyval_callback = callback;
  
      /* Open the pmix */
      pmix_util_keyval_yyin = fopen(keyval_filename, "r");
      if (NULL == pmix_util_keyval_yyin) {
          ret = PMIX_ERR_NOT_FOUND;
          goto cleanup;
      }
  
      pmix_util_keyval_parse_done = false;
      pmix_util_keyval_yynewlines = 1;
      pmix_util_keyval_init_buffer(pmix_util_keyval_yyin);
      while (!pmix_util_keyval_parse_done) {
          val = (pmix_keyval_parse_state_t) pmix_util_keyval_yylex();
          switch (val) {
          case PMIX_UTIL_KEYVAL_PARSE_DONE:
              /* This will also set pmix_util_keyval_parse_done to true, so just
                 break here */
              break;
  
          case PMIX_UTIL_KEYVAL_PARSE_NEWLINE:
              /* blank line!  ignore it */
              break;
  
          case PMIX_UTIL_KEYVAL_PARSE_SINGLE_WORD:
              parse_line();
              break;
  
          case PMIX_UTIL_KEYVAL_PARSE_MCAVAR:
          case PMIX_UTIL_KEYVAL_PARSE_ENVVAR:
          case PMIX_UTIL_KEYVAL_PARSE_ENVEQL:
              parse_line_new(val);
              break;
  
          default:
              /* anything else is an error */
              parse_error(1);
              break;
          }
      }
      fclose(pmix_util_keyval_yyin);
      pmix_util_keyval_yylex_destroy();
  
  cleanup:
      return ret;
  }
  
  static int parse_line(void)
  {
      pmix_keyval_parse_state_t val;
  
      pmix_util_keyval_parse_lineno = pmix_util_keyval_yylineno;
  
      /* Save the name name */
      if (key_buffer_len < strlen(pmix_util_keyval_yytext) + 1) {
          char *tmp;
          key_buffer_len = strlen(pmix_util_keyval_yytext) + 1;
          tmp = (char *) realloc(key_buffer, key_buffer_len);
          if (NULL == tmp) {
              free(key_buffer);
              key_buffer_len = 0;
              key_buffer = NULL;
              return PMIX_ERR_OUT_OF_RESOURCE;
          }
          key_buffer = tmp;
      }
  
      pmix_strncpy(key_buffer, pmix_util_keyval_yytext, key_buffer_len - 1);
  
      /* The first thing we have to see is an "=" */
  
      val = pmix_util_keyval_yylex();
      if (pmix_util_keyval_parse_done || PMIX_UTIL_KEYVAL_PARSE_EQUAL != val) {
          parse_error(2);
          return PMIX_ERROR;
      }
  
      /* Next we get the value */
  
      val = pmix_util_keyval_yylex();
      if (PMIX_UTIL_KEYVAL_PARSE_SINGLE_WORD == val || PMIX_UTIL_KEYVAL_PARSE_VALUE == val) {
          keyval_callback(key_buffer, pmix_util_keyval_yytext);
  
          /* Now we need to see the newline */
  
          val = pmix_util_keyval_yylex();
          if (PMIX_UTIL_KEYVAL_PARSE_NEWLINE == val || PMIX_UTIL_KEYVAL_PARSE_DONE == val) {
              return PMIX_SUCCESS;
          }
      }
  
      /* Did we get an EOL or EOF? */
  
      else if (PMIX_UTIL_KEYVAL_PARSE_DONE == val || PMIX_UTIL_KEYVAL_PARSE_NEWLINE == val) {
          keyval_callback(key_buffer, NULL);
          return PMIX_SUCCESS;
      }
  
      /* Nope -- we got something unexpected.  Bonk! */
      parse_error(3);
      return PMIX_ERROR;
  }
  
  static void parse_error(int num)
  {
      /* JMS need better error/warning message here */
      pmix_output(0, "keyval parser: error %d reading file %s at line %d:\n  %s\n", num,
                  keyval_filename, pmix_util_keyval_yynewlines, pmix_util_keyval_yytext);
  }
  
  int pmix_util_keyval_save_internal_envars(pmix_keyval_parse_fn_t callback)
  {
      if (NULL != env_str && 0 < strlen(env_str)) {
          callback("mca_base_env_list_internal", env_str);
          free(env_str);
          env_str = NULL;
      }
      return PMIX_SUCCESS;
  }
  
  static void trim_name(char *buffer, const char *prefix, const char *suffix)
  {
      char *pchr, *echr;
      size_t buffer_len;
  
      if (NULL == buffer) {
          return;
      }
  
      buffer_len = strlen(buffer);
  
      pchr = buffer;
      if (NULL != prefix) {
          size_t prefix_len = strlen(prefix);
  
          if (0 == strncmp(buffer, prefix, prefix_len)) {
              pchr += prefix_len;
          }
      }
  
      /* trim spaces at the beginning */
      while (isspace(*pchr)) {
          pchr++;
      }
  
      /* trim spaces at the end */
      echr = buffer + buffer_len;
      while (echr > buffer && isspace(*(echr - 1))) {
          echr--;
      }
      echr[0] = '\0';
  
      if (NULL != suffix && (uintptr_t)(echr - buffer) > strlen(suffix)) {
          size_t suffix_len = strlen(suffix);
  
          echr -= suffix_len;
  
          if (0 == strncmp(echr, suffix, strlen(suffix))) {
              do {
                  echr--;
              } while (isspace(*echr));
              echr[1] = '\0';
          }
      }
  
      if (buffer != pchr) {
          /* move the trimmed string to the beginning of the buffer */
          memmove(buffer, pchr, strlen(pchr) + 1);
      }
  }
  
  static int save_param_name(void)
  {
      if (key_buffer_len < strlen(pmix_util_keyval_yytext) + 1) {
          char *tmp;
          key_buffer_len = strlen(pmix_util_keyval_yytext) + 1;
          tmp = (char *) realloc(key_buffer, key_buffer_len);
          if (NULL == tmp) {
              free(key_buffer);
              key_buffer_len = 0;
              key_buffer = NULL;
              return PMIX_ERR_OUT_OF_RESOURCE;
          }
          key_buffer = tmp;
      }
  
      pmix_strncpy(key_buffer, pmix_util_keyval_yytext, key_buffer_len - 1);
  
      return PMIX_SUCCESS;
  }
  
  static int add_to_env_str(char *var, char *val)
  {
      int sz, varsz = 0, valsz = 0, new_envsize;
      void *tmp;
  
      if (NULL == var) {
          return PMIX_ERR_BAD_PARAM;
      }
  
      varsz = strlen(var);
      if (NULL != val) {
          valsz = strlen(val);
          /* account for '=' */
          valsz += 1;
      }
      sz = 0;
      if (NULL != env_str) {
          sz = strlen(env_str);
          /* account for ';' */
          sz += 1;
      }
      /* add required new size incl NULL byte */
      sz += varsz + valsz + 1;
  
      /* make sure we have sufficient space */
      new_envsize = envsize;
      while (new_envsize <= sz) {
          new_envsize *= 2;
      }
  
      if (NULL != env_str) {
          if (new_envsize > envsize) {
              tmp = realloc(env_str, new_envsize);
              if (NULL == tmp) {
                  return PMIX_ERR_OUT_OF_RESOURCE;
              }
              env_str = tmp;
          }
          strcat(env_str, ";");
      } else {
          env_str = calloc(1, new_envsize);
          if (NULL == env_str) {
              return PMIX_ERR_OUT_OF_RESOURCE;
          }
      }
      envsize = new_envsize;
  
      strcat(env_str, var);
      if (NULL != val) {
          strcat(env_str, "=");
          strcat(env_str, val);
      }
  
      return PMIX_SUCCESS;
  }
  
  static int parse_line_new(pmix_keyval_parse_state_t first_val)
  {
      pmix_keyval_parse_state_t val;
      char *tmp;
      int rc;
  
      val = first_val;
      while (PMIX_UTIL_KEYVAL_PARSE_NEWLINE != val && PMIX_UTIL_KEYVAL_PARSE_DONE != val) {
          rc = save_param_name();
          if (PMIX_SUCCESS != rc) {
              return rc;
          }
  
          if (PMIX_UTIL_KEYVAL_PARSE_MCAVAR == val) {
              trim_name(key_buffer, "-mca", NULL);
              trim_name(key_buffer, "--mca", NULL);
  
              val = pmix_util_keyval_yylex();
              if (PMIX_UTIL_KEYVAL_PARSE_VALUE == val) {
                  if (NULL != pmix_util_keyval_yytext) {
                      tmp = strdup(pmix_util_keyval_yytext);
                      if ('\'' == tmp[0] || '\"' == tmp[0]) {
                          trim_name(tmp, "\'", "\'");
                          trim_name(tmp, "\"", "\"");
                      }
                      keyval_callback(key_buffer, tmp);
                      free(tmp);
                  }
              } else {
                  parse_error(4);
                  return PMIX_ERROR;
              }
          } else if (PMIX_UTIL_KEYVAL_PARSE_ENVEQL == val) {
              trim_name(key_buffer, "-x", "=");
              trim_name(key_buffer, "--x", NULL);
  
              val = pmix_util_keyval_yylex();
              if (PMIX_UTIL_KEYVAL_PARSE_VALUE == val) {
                  add_to_env_str(key_buffer, pmix_util_keyval_yytext);
              } else {
                  parse_error(5);
                  return PMIX_ERROR;
              }
          } else if (PMIX_UTIL_KEYVAL_PARSE_ENVVAR == val) {
              trim_name(key_buffer, "-x", "=");
              trim_name(key_buffer, "--x", NULL);
              add_to_env_str(key_buffer, NULL);
          } else {
              /* we got something unexpected.  Bonk! */
              parse_error(6);
              return PMIX_ERROR;
          }
  
          val = pmix_util_keyval_yylex();
      }
  
      return PMIX_SUCCESS;
  }
  