Menu

src-local/case-params.h

Case Parameter Parsing Helper

Simple parser for key = value parameter files used by simulation cases.

#ifndef CASE_PARAMS_H
#define CASE_PARAMS_H

#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PARAM_KIND_INT
#define PARAM_KIND_DOUBLE

typedef struct {
  const char * key;
  void * target;
  int kind;
  int required;
  int seen;
} paramEntry;

static char * trimParamWhitespace (char * s) {
  while (*s && isspace((unsigned char) *s))
    s++;

  if (*s == '\0')
    return s;

  char * end = s + strlen(s) - 1;
  while (end >= s && isspace((unsigned char) *end))
    *end-- = '\0';

  return s;
}

static int parseCaseParams (const char * filename, paramEntry * entries,
                            size_t n_entries) {
  FILE * fp = fopen(filename, "r");
  if (!fp) {
    fprintf(ferr, "Could not open params file: %s\n", filename);
    return 0;
  }

  for (size_t i = 0; i < n_entries; i++)
    entries[i].seen = 0;

  char line[512];
  int line_no = 0;

  while (fgets(line, sizeof(line), fp)) {
    line_no++;

    char * text = trimParamWhitespace(line);
    if (*text == '\0' || *text == '#' || *text == ';')
      continue;

    char * hash = strchr(text, '#');
    if (hash)
      *hash = '\0';

    char * slash = strstr(text, "//");
    if (slash)
      *slash = '\0';

    text = trimParamWhitespace(text);
    if (*text == '\0')
      continue;

    char * eq = strchr(text, '=');
    if (!eq) {
      fprintf(ferr, "Malformed line %d in %s\n", line_no, filename);
      fclose(fp);
      return 0;
    }

    *eq = '\0';
    char * key = trimParamWhitespace(text);
    char * value = trimParamWhitespace(eq + 1);

    if (*key == '\0' || *value == '\0') {
      fprintf(ferr, "Invalid key/value at line %d in %s\n", line_no,
              filename);
      fclose(fp);
      return 0;
    }

    int matched = 0;
    for (size_t i = 0; i < n_entries; i++) {
      if (strcmp(key, entries[i].key))
        continue;

      matched = 1;
      entries[i].seen = 1;
      errno = 0;

      if (entries[i].kind == PARAM_KIND_INT) {
        char * end = NULL;
        long parsed = strtol(value, &end, 10);
        if (errno || !end || *end != '\0') {
          fprintf(ferr,
                  "Invalid integer for %s at line %d in %s\n",
                  key, line_no, filename);
          fclose(fp);
          return 0;
        }
        if (parsed < INT_MIN || parsed > INT_MAX) {
          fprintf(ferr,
                  "Integer out of range for %s at line %d"
                  " in %s\n",
                  key, line_no, filename);
          fclose(fp);
          return 0;
        }
        *((int *) entries[i].target) = (int) parsed;
      }
      else {
        char * end = NULL;
        double parsed = strtod(value, &end);
        if (errno || !end || *end != '\0') {
          fprintf(ferr, "Invalid number for %s at line %d in %s\n", key,
                  line_no, filename);
          fclose(fp);
          return 0;
        }
        *((double *) entries[i].target) = parsed;
      }
      break;
    }

    if (!matched)
      fprintf(ferr, "Ignoring unknown key '%s' in %s\n", key, filename);
  }

  fclose(fp);

  for (size_t i = 0; i < n_entries; i++) {
    if (entries[i].required && !entries[i].seen) {
      fprintf(ferr, "Missing required key '%s' in %s\n", entries[i].key,
              filename);
      return 0;
    }
  }

  return 1;
}

#endif