diff options
| author | Markus Uhlin <markus@nifty-networks.net> | 2025-10-18 13:15:15 +0200 |
|---|---|---|
| committer | Markus Uhlin <markus@nifty-networks.net> | 2025-10-18 13:15:15 +0200 |
| commit | 207978267241afb74ce7132ed9132c2467850941 (patch) | |
| tree | ad14db590a01cc52c9582e23e45e911868029fd5 | |
| parent | fd39305a6d1b05f18096c31c2abf083e6839eba1 (diff) | |
Config file interpreter
| -rw-r--r-- | FICS/build.mk | 2 | ||||
| -rw-r--r-- | FICS/interpreter.cpp | 196 | ||||
| -rw-r--r-- | FICS/interpreter.h | 57 |
3 files changed, 255 insertions, 0 deletions
diff --git a/FICS/build.mk b/FICS/build.mk index 5c2e4f7..7355421 100644 --- a/FICS/build.mk +++ b/FICS/build.mk @@ -17,6 +17,7 @@ OBJS = $(SRC_DIR)adminproc.o\ $(SRC_DIR)formula.o\ $(SRC_DIR)gamedb.o\ $(SRC_DIR)gameproc.o\ + $(SRC_DIR)interpreter.o\ $(SRC_DIR)iset.o\ $(SRC_DIR)legal.o\ $(SRC_DIR)legal2.o\ @@ -52,6 +53,7 @@ SRCS = $(SRC_DIR)adminproc.c\ $(SRC_DIR)formula.c\ $(SRC_DIR)gamedb.c\ $(SRC_DIR)gameproc.c\ + $(SRC_DIR)interpreter.cpp\ $(SRC_DIR)iset.cpp\ $(SRC_DIR)legal.c\ $(SRC_DIR)legal2.c\ diff --git a/FICS/interpreter.cpp b/FICS/interpreter.cpp new file mode 100644 index 0000000..4894c2f --- /dev/null +++ b/FICS/interpreter.cpp @@ -0,0 +1,196 @@ +// SPDX-FileCopyrightText: 2012-2025 Markus Uhlin <maxxe@rpblc.net> +// SPDX-License-Identifier: ISC + +#include <err.h> +#include <errno.h> +#include <iostream> +#include <stdexcept> + +#include "interpreter.h" +#include "utils.h" + +const char g_fgets_nullret_err1[70] = "error: fgets() returned null and the " + "error indicator is set"; +const char g_fgets_nullret_err2[70] = "error: fgets() returned null for an " + "unknown reason"; + +static const char ArgBegin = '"'; +static const char ArgEnd = '"'; +static const char CommentChar = '#'; + +static const size_t identifier_maxSize = 64; +static const size_t argument_maxSize = 512; + +/** + * Copy identifier + */ +static char * +copy_identifier(const char *&id) +{ + size_t count = identifier_maxSize; + char *dest_buf = new char[count + 1]; + char *dest = &dest_buf[0]; + + while ((isalnum(*id) || *id == '_') && count > 1) + *dest++ = *id++, count--; + + *dest = '\0'; + + if (count == 1) + errx(1, "%s: fatal: string was truncated", __func__); + return dest_buf; +} + +/** + * Copy argument + */ +//lint -sem(copy_argument, r_null) +static char * +copy_argument(const char *&arg) +{ + bool inside_arg = true; + size_t count = argument_maxSize; + char *dest_buf = new char[count + 1]; + char *dest = &dest_buf[0]; + + while (*arg && count > 1) { + if (*arg == ArgEnd) { + inside_arg = false; + arg++; + break; + } + + *dest++ = *arg++, count--; + } + + *dest = '\0'; + + if (inside_arg && count == 1) + errx(1, "%s: fatal: string was truncated", __func__); + if (inside_arg) { + delete[] dest_buf; + return nullptr; + } + return dest_buf; +} + +static void +clean_up(char *id, char *arg) +{ + delete[] id; + delete[] arg; +} + +/** + * Interpreter + * + * @param in Context structure + * @return Void + * + * An interpreter for configuration files. The context structure + * contains the data to be passed to the interpreter. + */ +void +Interpreter(const struct Interpreter_in *in) +{ + char *id = nullptr; + char *arg = nullptr; + + if (in == nullptr) + errx(1, "%s: invalid argument", __func__); + + try { + const char *cp = &in->line[0]; + + if (!isalnum(*cp) && *cp != '_') + throw std::runtime_error("unexpected leading " + "character"); + id = copy_identifier(cp); + adv_while_isspace(&cp); + if (*cp++ != '=') { + throw std::runtime_error("expected assignment " + "operator"); + } + + adv_while_isspace(&cp); + if (*cp++ != ArgBegin) + throw std::runtime_error("expected arg begin"); + else if ((arg = copy_argument(cp)) == nullptr) + throw std::runtime_error("unterminated argument"); + + adv_while_isspace(&cp); + if (*cp++ != ';') + throw std::runtime_error("no line terminator!"); + + adv_while_isspace(&cp); + if (*cp && *cp != CommentChar) { + throw std::runtime_error("implicit data after " + "line terminator!"); + } else if (!(in->validator_func(id))) { +#if IGNORE_UNRECOGNIZED_IDENTIFIERS + /* ignore */; +#else + throw std::runtime_error("no such identifier"); +#endif + } else if ((errno = in->install_func(id, arg)) != 0) { + throw std::runtime_error("install error"); + } + } catch (const std::bad_alloc &e) { + std::cerr << "out of memory: " << e.what() << '\n'; + clean_up(id, arg); + abort(); + } catch (const std::runtime_error &e) { + std::cerr << '\t' << in->line << '\n'; + + if (strings_match(e.what(), "install error")) { + warn("%s:%ld: error: " + "install_func returned %d", in->path, in->line_num, + errno); + } else { + warnx("%s:%ld: error: %s", in->path, in->line_num, + e.what()); + } + + clean_up(id, arg); + abort(); + } catch (...) { + std::cerr << "unknown exception -- should not be reached\n"; + clean_up(id, arg); + abort(); + } + + clean_up(id, arg); +} + +void +Interpreter_processAllLines(FILE *fp, const char *path, Interpreter_vFunc func1, + Interpreter_instFunc func2) +{ + char buf[MAXLINE] = { '\0' }; + long int line_num = 0; + + while (fgets(buf, sizeof buf, fp) != nullptr) { + char *line; + const char *cp; + struct Interpreter_in in; + + cp = &buf[0]; + adv_while_isspace(&cp); + if (strings_match(cp, "") || *cp == CommentChar) { + line_num++; + continue; + } + + if ((line = eattailwhite(xstrdup(cp))) == nullptr) + errx(1, "%s: unexpected null pointer", __func__); + + in.path = const_cast<char *>(path); + in.line = line; + in.line_num = ++line_num; + in.validator_func = func1; + in.install_func = func2; + Interpreter(&in); + + free(line); + } +} diff --git a/FICS/interpreter.h b/FICS/interpreter.h new file mode 100644 index 0000000..b807ad6 --- /dev/null +++ b/FICS/interpreter.h @@ -0,0 +1,57 @@ +#ifndef INTERPRETER_H +#define INTERPRETER_H + +#include <ctype.h> /* isspace */ +#include <stdbool.h> +#include <stdio.h> /* FILE */ +#include <string.h> + +#include "common.h" + +/* + * Set to 0 to turn off this feature. + */ +#define IGNORE_UNRECOGNIZED_IDENTIFIERS 1 + +#define MAXLINE 3200 + +enum setting_type { + TYPE_BOOLEAN, + TYPE_INTEGER, + TYPE_STRING +}; + +typedef bool (*Interpreter_vFunc)(const char *); +typedef int (*Interpreter_instFunc)(const char *, const char *); + +struct Interpreter_in { + char *path; + char *line; + long int line_num; + Interpreter_vFunc validator_func; + Interpreter_instFunc install_func; +}; + +__FICS_BEGIN_DECLS +extern const char g_fgets_nullret_err1[70]; +extern const char g_fgets_nullret_err2[70]; + +void Interpreter(const struct Interpreter_in *); +void Interpreter_processAllLines(FILE *, const char *, Interpreter_vFunc, + Interpreter_instFunc); +__FICS_END_DECLS + +static inline void +adv_while_isspace(const char **ptr) +{ + while (isspace(**ptr)) + (*ptr)++; +} + +static inline bool +strings_match(const char *str1, const char *str2) +{ + return (strcmp(str1, str2) == 0); +} + +#endif |
