parser.c 17.9 KB
Newer Older
1
2
/*******************************************************************************
 * This file is part of SWIFT.
3
 * Copyright (c) 2016 James Willis (james.s.willis@durham.ac.uk)
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 ******************************************************************************/

20
21
/* Config parameters. */
#include "../config.h"
22

23
/* Some standard headers. */
24
25
26
/* Needs to be included so that strtok returns char * instead of a int *. */
#include <string.h>
#include <stdlib.h>
James Willis's avatar
James Willis committed
27
#include <ctype.h>
28
29
30
31
32

/* This object's header. */
#include "parser.h"

/* Local headers. */
33
#include "error.h"
34
#include "common_io.h"
35

Matthieu Schaller's avatar
Matthieu Schaller committed
36
37
38
39
40
41
42
#define PARSER_COMMENT_STRING "#"
#define PARSER_COMMENT_CHAR '#'
#define PARSER_VALUE_CHAR ':'
#define PARSER_VALUE_STRING ":"
#define PARSER_START_OF_FILE "---"
#define PARSER_END_OF_FILE "..."

43
/* Private functions. */
James Willis's avatar
James Willis committed
44
45
static int count_char(const char *str, char val);
static int is_empty(const char *str);
46
static int count_indentation(const char *str);
James Willis's avatar
James Willis committed
47
static void parse_line(char *line, struct swift_params *params);
48
static void parse_value(char *line, struct swift_params *params);
James Willis's avatar
James Willis committed
49
50
static void parse_section_param(char *line, int *isFirstParam,
                                char *sectionName, struct swift_params *params);
51
52
static void find_duplicate_params(const struct swift_params *params,
                                  const char *param_name);
53
54
static void find_duplicate_section(const struct swift_params *params,
                                   const char *section_name);
Matthieu Schaller's avatar
Matthieu Schaller committed
55
static int lineNumber = 0;
56

57
/**
Matthieu Schaller's avatar
Matthieu Schaller committed
58
59
 * @brief Reads an input file and stores each parameter in a structure.
 *
60
61
62
63
 * @param file_name Name of file to be read
 * @param params Structure to be populated from file
 */

64
void parser_read_file(const char *file_name, struct swift_params *params) {
Matthieu Schaller's avatar
Matthieu Schaller committed
65
  /* Open file for reading */
66
  FILE *file = fopen(file_name, "r");
67

James Willis's avatar
James Willis committed
68
69
  /* Line to parsed. */
  char line[PARSER_MAX_LINE_SIZE];
James Willis's avatar
James Willis committed
70

71
  /* Initialise parameter count. */
72
73
  params->paramCount = 0;
  params->sectionCount = 0;
74
  strcpy(params->fileName, file_name);
Matthieu Schaller's avatar
Matthieu Schaller committed
75

76
  /* Check if parameter file exits. */
77
78
  if (file == NULL) {
    error("Error opening parameter file: %s", file_name);
Matthieu Schaller's avatar
Matthieu Schaller committed
79
  }
80

Matthieu Schaller's avatar
Matthieu Schaller committed
81
  /* Read until the end of the file is reached.*/
82
  while (!feof(file)) {
James Willis's avatar
James Willis committed
83
    if (fgets(line, PARSER_MAX_LINE_SIZE, file) != NULL) {
84
      lineNumber++;
James Willis's avatar
James Willis committed
85
86
      parse_line(line, params);
    }
Matthieu Schaller's avatar
Matthieu Schaller committed
87
  }
88

89
  fclose(file);
90
91
}

92
93
94
95
96
/**
 * @brief Counts the number of times a specific character appears in a string.
 *
 * @param str String to be checked
 * @param val Character to be counted
James Willis's avatar
James Willis committed
97
 *
Matthieu Schaller's avatar
Matthieu Schaller committed
98
 * @return Number of occurrences of val inside str
99
 */
100

James Willis's avatar
James Willis committed
101
static int count_char(const char *str, char val) {
Matthieu Schaller's avatar
Matthieu Schaller committed
102
  int count = 0;
103

Matthieu Schaller's avatar
Matthieu Schaller committed
104
105
106
107
  /* Check if the line contains the character */
  while (*str) {
    if (*str++ == val) ++count;
  }
108

Matthieu Schaller's avatar
Matthieu Schaller committed
109
  return count;
110
111
}

112
113
114
115
116
/**
 * @brief Counts the number of white spaces that prefix a string.
 *
 * @param str String to be checked
 *
Matthieu Schaller's avatar
Matthieu Schaller committed
117
 * @return Number of white spaces prefixing str
118
119
120
121
122
123
124
125
126
127
128
129
 */

static int count_indentation(const char *str) {
  int count = 0;

  /* Check if the line contains the character */
  while (*(++str) == ' ') {
    count++;
  }
  return count;
}

James Willis's avatar
James Willis committed
130
131
132
133
134
/**
 * @brief Checks if a string is empty.
 *
 * @param str String to be checked
 *
Matthieu Schaller's avatar
Matthieu Schaller committed
135
 * @return Returns 1 if str is empty, 0 otherwise
James Willis's avatar
James Willis committed
136
137
138
139
140
141
142
143
144
145
146
 */

static int is_empty(const char *str) {
  int retParam = 1;
  while (*str != '\0') {
    if (!isspace(*str)) {
      retParam = 0;
      break;
    }
    str++;
  }
James Willis's avatar
James Willis committed
147

James Willis's avatar
James Willis committed
148
149
150
  return retParam;
}

151
152
153
154
155
156
157
158
159
160
161
162
/**
 * @brief Look for duplicate parameters.
 *
 * @param params Structure that holds the parameters
 * @param param_name Name of parameter to be searched for
 */

static void find_duplicate_params(const struct swift_params *params,
                                  const char *param_name) {
  for (int i = 0; i < params->paramCount; i++) {
    /*strcmp returns 0 if both strings are the same.*/
    if (!strcmp(param_name, params->data[i].name)) {
163
164
      error("Invalid line:%d '%s', parameter is a duplicate.", lineNumber,
            param_name);
165
166
167
168
    }
  }
}

169
170
171
172
/**
 * @brief Look for duplicate sections.
 *
 * @param params Structure that holds the parameters
173
 * @param section_name Name of section to be searched for
174
175
176
 */

static void find_duplicate_section(const struct swift_params *params,
177
                                   const char *section_name) {
178
179
180
  for (int i = 0; i < params->sectionCount; i++) {
    /*strcmp returns 0 if both strings are the same.*/
    if (!strcmp(section_name, params->section[i].name)) {
181
182
      error("Invalid line:%d '%s', section is a duplicate.", lineNumber,
            section_name);
183
184
185
    }
  }
}
Matthieu Schaller's avatar
Matthieu Schaller committed
186
/**
187
188
 * @brief Parses a line from a file and stores any parameters in a structure.
 *
189
190
 * @param line Line to be parsed.
 * @param params Structure to be populated from file.
191
 */
192

James Willis's avatar
James Willis committed
193
194
195
196
197
static void parse_line(char *line, struct swift_params *params) {
  /* Parse line if it doesn't begin with a comment. */
  if (*line != PARSER_COMMENT_CHAR) {
    char trim_line[PARSER_MAX_LINE_SIZE];
    char tmp_str[PARSER_MAX_LINE_SIZE];
Matthieu Schaller's avatar
Matthieu Schaller committed
198
    char *token;
James Willis's avatar
James Willis committed
199

200
201
    /* Remove comments at the end of a line. */
    token = strtok(line, PARSER_COMMENT_STRING);
202
    strcpy(tmp_str, token);
203

James Willis's avatar
James Willis committed
204
    /* Check if the line is just white space. */
James Willis's avatar
James Willis committed
205
    if (!is_empty(tmp_str)) {
206
207
208
      /* Trim '\n' characters from string. */
      token = strtok(tmp_str, "\n");
      strcpy(trim_line, token);
James Willis's avatar
James Willis committed
209

James Willis's avatar
James Willis committed
210
211
      /* Check if the line contains a value and parse it. */
      if (strchr(trim_line, PARSER_VALUE_CHAR)) {
212
        parse_value(trim_line, params);
James Willis's avatar
James Willis committed
213
      }
214
215
      /* Check for invalid lines,not including the start and end of file. */
      /* Note: strcmp returns 0 if both strings are the same.*/
James Willis's avatar
James Willis committed
216
217
218
      else if (strcmp(trim_line, PARSER_START_OF_FILE) &&
               strcmp(trim_line, PARSER_END_OF_FILE)) {
        error("Invalid line:%d '%s'.", lineNumber, trim_line);
James Willis's avatar
James Willis committed
219
      }
220
221
222
223
224
225
226
227
228
229
230
231
    }
  }
}

/**
 * @brief Performs error checking and stores a parameter in a structure.
 *
 * @param line Line containing the parameter
 * @param params Structure to be written to
 *
 */

232
233
static void parse_value(char *line, struct swift_params *params) {
  static int inSection = 0;
Matthieu Schaller's avatar
Matthieu Schaller committed
234
235
  static char section[PARSER_MAX_LINE_SIZE]; /* Keeps track of current section
                                                name. */
236
237
  static int isFirstParam = 1;
  char tmpStr[PARSER_MAX_LINE_SIZE];
238
  char tmpSectionName[PARSER_MAX_LINE_SIZE];
239

240
241
  char *token;

242
  /* Check for more than one value on the same line. */
243
  if (count_char(line, PARSER_VALUE_CHAR) > 1) {
James Willis's avatar
James Willis committed
244
245
    error("Inavlid line:%d '%s', only one value allowed per line.", lineNumber,
          line);
246
247
  }

248
  /* Check that standalone parameters have correct indentation. */
James Willis's avatar
James Willis committed
249
250
251
252
253
  if (!inSection && *line == ' ') {
    error(
        "Invalid line:%d '%s', standalone parameter defined with incorrect "
        "indentation.",
        lineNumber, line);
254
255
  }

256
257
  /* Check that it is a parameter inside a section.*/
  if (*line == ' ' || *line == '\t') {
James Willis's avatar
James Willis committed
258
    parse_section_param(line, &isFirstParam, section, params);
Matthieu Schaller's avatar
Matthieu Schaller committed
259
  } else {/*Else it is the start of a new section or standalone parameter. */
260
261
    /* Take first token as the parameter name. */
    token = strtok(line, " :\t");
262
    strcpy(tmpStr, token);
263
264
265
266
267
268

    /* Take second token as the parameter value. */
    token = strtok(NULL, " #\n");

    /* If second token is NULL then the line must be a section heading. */
    if (token == NULL) {
269
270
      strcpy(tmpSectionName, tmpStr);
      strcat(tmpSectionName, PARSER_VALUE_STRING);
271

272
      /* Check for duplicate section name. */
273
274
275
276
277
278
      find_duplicate_section(params, tmpSectionName);

      /* Check for duplicate standalone parameter name used as a section name.
       */
      find_duplicate_params(params, tmpStr);

279
      strcpy(section, tmpSectionName);
280
281
282
283
284
285
286
      strcpy(params->section[params->sectionCount].name, tmpSectionName);
      if (params->sectionCount == PARSER_MAX_NO_OF_SECTIONS - 1) {
        error(
            "Maximal number of sections in parameter file reached. Aborting !");
      } else {
        params->sectionCount++;
      }
287
288
      inSection = 1;
      isFirstParam = 1;
289
    } else {
290
      /* Create string with standalone parameter name appended with ":" to aid
291
292
293
294
       * duplicate search as section names are stored with ":" at the end.*/
      strcpy(tmpSectionName, tmpStr);
      strcat(tmpSectionName, PARSER_VALUE_STRING);

295
      /* Check for duplicate parameter name. */
296
297
      find_duplicate_params(params, tmpStr);

298
      /* Check for duplicate section name used as standalone parameter name. */
299
300
      find_duplicate_section(params, tmpSectionName);

301
302
      /* Must be a standalone parameter so no need to prefix name with a
       * section. */
303
      strcpy(params->data[params->paramCount].name, tmpStr);
304
305
306
      strcpy(params->data[params->paramCount].value, token);
      if (params->paramCount == PARSER_MAX_NO_OF_PARAMS - 1) {
        error(
307
            "Maximal number of parameters in parameter file reached. Aborting "
308
309
310
311
            "!");
      } else {
        params->paramCount++;
      }
312
313
      inSection = 0;
      isFirstParam = 1;
314
    }
Matthieu Schaller's avatar
Matthieu Schaller committed
315
  }
316
317
}

318
/**
James Willis's avatar
James Willis committed
319
320
 * @brief Parses a parameter that appears in a section and stores it in a
 *structure.
321
322
323
324
325
326
327
328
 *
 * @param line Line containing the parameter
 * @param isFirstParam Shows if the first parameter of a section has been found
 * @param sectionName String containing the current section name
 * @param params Structure to be written to
 *
 */

James Willis's avatar
James Willis committed
329
330
331
static void parse_section_param(char *line, int *isFirstParam,
                                char *sectionName,
                                struct swift_params *params) {
332
333
334
335
  static int sectionIndent = 0;
  char tmpStr[PARSER_MAX_LINE_SIZE];
  char paramName[PARSER_MAX_LINE_SIZE];
  char *token;
James Willis's avatar
James Willis committed
336
337
338
339

  /* Count indentation of each parameter and check that it
   * is consistent with the first parameter in the section. */
  if (*isFirstParam) {
340
341
    sectionIndent = count_indentation(line);
    *isFirstParam = 0;
James Willis's avatar
James Willis committed
342
343
344
  } else if (count_indentation(line) != sectionIndent) {
    error("Invalid line:%d '%s', parameter has incorrect indentation.",
          lineNumber, line);
345
  }
James Willis's avatar
James Willis committed
346

347
348
349
350
351
352
353
354
355
356
357
  /* Take first token as the parameter name and trim leading white space. */
  token = strtok(line, " :\t");
  strcpy(tmpStr, token);

  /* Take second token as the parameter value. */
  token = strtok(NULL, " #\n");

  /* Prefix the parameter name with its section name and
   * copy it into the parameter structure. */
  strcpy(paramName, sectionName);
  strcat(paramName, tmpStr);
358
359

  /* Check for duplicate parameter name. */
360
361
  find_duplicate_params(params, paramName);

362
  strcpy(params->data[params->paramCount].name, paramName);
363
364
  strcpy(params->data[params->paramCount].value, token);
  if (params->paramCount == PARSER_MAX_NO_OF_PARAMS - 1) {
365
    error("Maximal number of parameters in parameter file reached. Aborting !");
366
367
368
  } else {
    params->paramCount++;
  }
369
370
}

Matthieu Schaller's avatar
Matthieu Schaller committed
371
/**
372
373
374
375
 * @brief Retrieve integer parameter from structure.
 *
 * @param params Structure that holds the parameters
 * @param name Name of the parameter to be found
376
 * @return Value of the parameter found
377
 */
378
int parser_get_param_int(const struct swift_params *params, const char *name) {
379

380
  char str[PARSER_MAX_LINE_SIZE];
381
  int retParam = 0;
382

383
  for (int i = 0; i < params->paramCount; i++) {
Matthieu Schaller's avatar
Matthieu Schaller committed
384
385
386
    /*strcmp returns 0 if both strings are the same.*/
    if (!strcmp(name, params->data[i].name)) {
      /* Check that exactly one number is parsed. */
387
      if (sscanf(params->data[i].value, "%d%s", &retParam, str) != 1) {
Matthieu Schaller's avatar
Matthieu Schaller committed
388
389
390
391
392
        error(
            "Tried parsing int '%s' but found '%s' with illegal integer "
            "characters '%s'.",
            params->data[i].name, params->data[i].value, str);
      }
393

394
      return retParam;
Matthieu Schaller's avatar
Matthieu Schaller committed
395
396
    }
  }
397

398
399
  error("Cannot find '%s' in the structure, in file '%s'.", name,
        params->fileName);
400
  return 0;
401
402
}

403
404
405
406
407
408
409
410
411
412
413
414
415
/**
 * @brief Retrieve char parameter from structure.
 *
 * @param params Structure that holds the parameters
 * @param name Name of the parameter to be found
 * @return Value of the parameter found
 */
char parser_get_param_char(const struct swift_params *params,
                           const char *name) {

  char str[PARSER_MAX_LINE_SIZE];
  char retParam = 0;

416
  for (int i = 0; i < params->paramCount; i++) {
417
418
419
420
421
422
423
424
425
426
427
428
429
430
    /*strcmp returns 0 if both strings are the same.*/
    if (!strcmp(name, params->data[i].name)) {
      /* Check that exactly one number is parsed. */
      if (sscanf(params->data[i].value, "%c%s", &retParam, str) != 1) {
        error(
            "Tried parsing char '%s' but found '%s' with illegal char "
            "characters '%s'.",
            params->data[i].name, params->data[i].value, str);
      }

      return retParam;
    }
  }

431
432
  error("Cannot find '%s' in the structure, in file '%s'.", name,
        params->fileName);
433
434
435
  return 0;
}

Matthieu Schaller's avatar
Matthieu Schaller committed
436
/**
437
438
439
440
 * @brief Retrieve float parameter from structure.
 *
 * @param params Structure that holds the parameters
 * @param name Name of the parameter to be found
441
 * @return Value of the parameter found
442
 */
443
444
float parser_get_param_float(const struct swift_params *params,
                             const char *name) {
445

446
  char str[PARSER_MAX_LINE_SIZE];
447
  float retParam = 0.f;
448

449
  for (int i = 0; i < params->paramCount; i++) {
Matthieu Schaller's avatar
Matthieu Schaller committed
450
    /*strcmp returns 0 if both strings are the same.*/
451
    if (!strcmp(name, params->data[i].name)) {
Matthieu Schaller's avatar
Matthieu Schaller committed
452
      /* Check that exactly one number is parsed. */
453
      if (sscanf(params->data[i].value, "%f%s", &retParam, str) != 1) {
Matthieu Schaller's avatar
Matthieu Schaller committed
454
455
456
        error(
            "Tried parsing float '%s' but found '%s' with illegal float "
            "characters '%s'.",
457
            params->data[i].name, params->data[i].value, str);
Matthieu Schaller's avatar
Matthieu Schaller committed
458
459
      }

460
      return retParam;
Matthieu Schaller's avatar
Matthieu Schaller committed
461
462
    }
  }
463

464
465
  error("Cannot find '%s' in the structure, in file '%s'.", name,
        params->fileName);
466
  return 0.f;
467
468
}

Matthieu Schaller's avatar
Matthieu Schaller committed
469
/**
470
471
472
473
 * @brief Retrieve double parameter from structure.
 *
 * @param params Structure that holds the parameters
 * @param name Name of the parameter to be found
474
 * @return Value of the parameter found
475
 */
476
477
double parser_get_param_double(const struct swift_params *params,
                               const char *name) {
478

479
  char str[PARSER_MAX_LINE_SIZE];
480
  double retParam = 0.;
Matthieu Schaller's avatar
Matthieu Schaller committed
481

482
  for (int i = 0; i < params->paramCount; i++) {
Matthieu Schaller's avatar
Matthieu Schaller committed
483
    /*strcmp returns 0 if both strings are the same.*/
484
    if (!strcmp(name, params->data[i].name)) {
Matthieu Schaller's avatar
Matthieu Schaller committed
485
      /* Check that exactly one number is parsed. */
486
      if (sscanf(params->data[i].value, "%lf%s", &retParam, str) != 1) {
487
488
489
490
        error(
            "Tried parsing double '%s' but found '%s' with illegal double "
            "characters '%s'.",
            params->data[i].name, params->data[i].value, str);
Matthieu Schaller's avatar
Matthieu Schaller committed
491
      }
492
      return retParam;
Matthieu Schaller's avatar
Matthieu Schaller committed
493
494
    }
  }
495

496
497
  error("Cannot find '%s' in the structure, in file '%s'.", name,
        params->fileName);
498
  return 0.;
499
500
}

Matthieu Schaller's avatar
Matthieu Schaller committed
501
/**
502
503
504
505
 * @brief Retrieve string parameter from structure.
 *
 * @param params Structure that holds the parameters
 * @param name Name of the parameter to be found
506
 * @param retParam (return) Value of the parameter found
507
 */
508
509
void parser_get_param_string(const struct swift_params *params,
                             const char *name, char *retParam) {
510
  for (int i = 0; i < params->paramCount; i++) {
Matthieu Schaller's avatar
Matthieu Schaller committed
511
512
513
514
515
516
    /*strcmp returns 0 if both strings are the same.*/
    if (!strcmp(name, params->data[i].name)) {
      strcpy(retParam, params->data[i].value);
      return;
    }
  }
James Willis's avatar
James Willis committed
517
518

  error("Cannot find '%s' in the structure.", name);
519
520
}

Matthieu Schaller's avatar
Matthieu Schaller committed
521
/**
522
523
524
525
 * @brief Prints the contents of the parameter structure.
 *
 * @param params Structure that holds the parameters
 */
526
void parser_print_params(const struct swift_params *params) {
Matthieu Schaller's avatar
Matthieu Schaller committed
527
528
529
  printf("\n--------------------------\n");
  printf("|  SWIFT Parameter File  |\n");
  printf("--------------------------\n");
530

531
  for (int i = 0; i < params->paramCount; i++) {
Matthieu Schaller's avatar
Matthieu Schaller committed
532
533
534
    printf("Parameter name: %s\n", params->data[i].name);
    printf("Parameter value: %s\n", params->data[i].value);
  }
535
}
536

537
538
539
/**
 * @brief Write the contents of the parameter structure to a file in YAML
 *format.
540
541
542
543
 *
 * @param params Structure that holds the parameters
 * @param file_name Name of file to be written
 */
544
void parser_write_params_to_file(const struct swift_params *params,
545
546
547
548
549
                                 const char *file_name) {
  FILE *file = fopen(file_name, "w");
  char section[PARSER_MAX_LINE_SIZE];
  char param_name[PARSER_MAX_LINE_SIZE];
  char *token;
550

551
552
  /* Start of file identifier in YAML. */
  fprintf(file, "%s\n", PARSER_START_OF_FILE);
553

554
  for (int i = 0; i < params->paramCount; i++) {
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
    /* Check that the parameter name contains a section name. */
    if (strchr(params->data[i].name, PARSER_VALUE_CHAR)) {
      /* Copy the parameter name into a temporary string and find the section
       * name. */
      strcpy(param_name, params->data[i].name);
      token = strtok(param_name, PARSER_VALUE_STRING);

      /* If a new section name is found print it to the file. */
      if (strcmp(token, section)) {
        strcpy(section, token);
        fprintf(file, "\n%s%c\n", section, PARSER_VALUE_CHAR);
      }

      /* Remove white space from parameter name and write it to the file. */
      token = strtok(NULL, " #\n");

571
      fprintf(file, "  %s%c %s\n", token, PARSER_VALUE_CHAR,
572
573
574
575
              params->data[i].value);
    } else {
      fprintf(file, "\n%s%c %s\n", params->data[i].name, PARSER_VALUE_CHAR,
              params->data[i].value);
576
    }
577
  }
578

579
580
581
582
  /* End of file identifier in YAML. */
  fprintf(file, PARSER_END_OF_FILE);

  fclose(file);
583
}
584
585
586
587
588
589
590
591

#if defined(HAVE_HDF5)
void parser_write_params_to_hdf5(const struct swift_params *params, hid_t grp) {

  for (int i = 0; i < params->paramCount; i++)
    writeAttribute_s(grp, params->data[i].name, params->data[i].value);
}
#endif