commit 2a95e02dd8faf31a06a5042279dc1634b769f188
parent e32936df9a3d7dd53706fa9fdc7dc61ac65b400a
Author: Vincent Forest <vincent.forest@meso-star.com>
Date: Thu, 29 Jan 2026 16:39:25 +0100
Start implementation of the sln-build tool
Implement argument analysis
Diffstat:
| M | .gitignore | | | 1 | + |
| M | Makefile | | | 37 | ++++++++++++++++++++++++++++++++++--- |
| A | src/sln_build.c | | | 288 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
3 files changed, 323 insertions(+), 3 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -6,6 +6,7 @@
*~
.config
.gitignore
+sln-build
tags
tags
test_*
diff --git a/Makefile b/Makefile
@@ -26,7 +26,7 @@ LIBNAME_SHARED = libsln.so
LIBNAME = $(LIBNAME_$(LIB_TYPE))
default: library
-all: library tests
+all: library tests utils
################################################################################
# Library building
@@ -79,6 +79,38 @@ libsln.o: $(OBJ)
$(CC) $(CFLAGS_LIB) -c $< -o $@
################################################################################
+# Utils
+################################################################################
+UTIL_SRC = src/sln_build.c
+UTIL_OBJ = $(UTIL_SRC:.c=.o)
+UTIL_DEP = $(UTIL_SRC:.c=.d)
+
+PKG_CONFIG_LOCAL = PKG_CONFIG_PATH="./:$${PKG_CONFIG_PATH}" $(PKG_CONFIG)
+
+INCS_UTIL = $$($(PKG_CONFIG_LOCAL) $(PCFLAGS) --cflags rsys sln-local)
+LIBS_UTIL = $$($(PKG_CONFIG_LOCAL) $(PCFLAGS) --libs rsys sln-local)
+
+CFLAGS_UTIL = -std=c89 $(CFLAGS_EXE) $(INCS_UTIL)
+LDFLAGS_UTIL = $(LDFLAGS_EXE) $(LIBS_UTIL)
+
+utils: library $(UTIL_DEP)
+ @$(MAKE) -fMakefile \
+ $$(for i in $(UTIL_DEP); do printf -- '-f%s\n' "$${i}"; done) \
+ sln-build
+
+sln-build: config.mk sln-local.pc src/sln_build.o $(LIBNAME)
+ $(CC) $(CFLAGS_UTIL) -o $@ src/sln_build.o $(LDFLAGS_UTIL)
+
+$(UTIL_DEP): config.mk sln-local.pc
+ $(CC) $(CFLAGS_UTIL) -MM -MT "$(@:.d=.o) $@" $(@:.d=.c) -MF $@
+
+$(UTIL_OBJ): config.mk sln-local.pc
+ $(CC) $(CFLAGS_UTIL) -c $(@:.o=.c) -o $@
+
+clean_utils:
+ rm -f $(UTIL_OBJ) $(UTIL_DEP) sln-build
+
+################################################################################
# Installation
################################################################################
pkg:
@@ -121,7 +153,7 @@ uninstall:
rm -f "$(DESTDIR)$(PREFIX)/share/doc/star-line/COPYING"
rm -f "$(DESTDIR)$(PREFIX)/share/doc/star-line/README.md"
-clean: clean_test
+clean: clean_test clean_utils
rm -f $(OBJ) $(DEP) $(LIBNAME)
rm -f .config libsln.o sln.pc sln-local.pc
@@ -139,7 +171,6 @@ TEST_OBJ = $(TEST_SRC:.c=.o)
TEST_DEP = $(TEST_SRC:.c=.d)
TEST_TGT = $(TEST_SRC:.c=.t)
-PKG_CONFIG_LOCAL = PKG_CONFIG_PATH="./:$${PKG_CONFIG_PATH}" $(PKG_CONFIG)
INCS_TEST = $$($(PKG_CONFIG_LOCAL) $(PCFLAGS) --cflags rsys sln-local shtr)
LIBS_TEST = $$($(PKG_CONFIG_LOCAL) $(PCFLAGS) --libs rsys sln-local shtr)
diff --git a/src/sln_build.c b/src/sln_build.c
@@ -0,0 +1,288 @@
+/* Copyright (C) 2022, 2026 |Méso|Star> (contact@meso-star.com)
+ * Copyright (C) 2026 Université de Lorraine
+ * Copyright (C) 2022 Centre National de la Recherche Scientifique
+ * Copyright (C) 2022 Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#define _POSIX_C_SOURCE 200809L /* strtok_r */
+
+#include "sln.h"
+
+#include <rsys/cstr.h>
+#include <rsys/mem_allocator.h>
+#include <rsys/rsys.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h> /* getopt */
+
+enum line_list_format {
+ LINE_LIST_HITRAN,
+ LINE_LIST_SHTR,
+ LINE_LIST_FORMAT_COUNT__
+};
+
+struct args {
+ /* Spectroscopic parameters */
+ const char* lines;
+ const char* molparams;
+ enum sln_line_profile line_profile;
+
+ const char* output;
+
+ /* Thermodynamic properties */
+ const char* mixture;
+ double pressure; /* [atm] */
+ double temperature; /* [K] */
+
+ /* Polyline */
+ double mesh_decimation_err;
+ unsigned nvertices_hint;
+ enum sln_mesh_type mesh_type;
+
+ /* Miscellaneous */
+ int quit;
+ int verbose;
+ enum line_list_format line_format;
+};
+#define ARGS_DEFAULT__ { \
+ /* Spectroscopic parameters */ \
+ NULL, /* line list */ \
+ NULL, /* Isotopologue metadata */ \
+ SLN_LINE_PROFILE_VOIGT, /* Line profile */ \
+ \
+ NULL, /* Output */ \
+ \
+ /* Thermodynamic properties */ \
+ NULL, \
+ -1, /* Pressure [atm] */ \
+ -1, /* Temperature [K] */ \
+ \
+ /* Polyline */ \
+ 0.01, \
+ 16, \
+ SLN_MESH_UPPER, \
+ \
+ /* Miscellaneous */ \
+ 0, /* Quit */ \
+ 0, /* Verbose */ \
+ LINE_LIST_HITRAN /* lines_format */ \
+}
+static const struct args ARGS_DEFAULT = ARGS_DEFAULT__;
+
+/*******************************************************************************
+ * Helper functions
+ ******************************************************************************/
+static void
+usage(FILE* stream)
+{
+ fprintf(stream,
+"usage: sln-build [-hsv] [-e polyline_opt[:polyline_opt ...]] [-l line_profile]\n"
+" [-o accel_struct] -P pressure -T temperature -m molparams\n"
+" -x mixture [lines]\n");
+}
+
+static res_T
+parse_line_profile(const char* str, enum sln_line_profile* profile)
+{
+ res_T res = RES_OK;
+ ASSERT(str && profile);
+
+ if(!strcmp(str, "voigt")) {
+ *profile = SLN_LINE_PROFILE_VOIGT;
+
+ } else {
+ fprintf(stderr, "invalid line profile '%s'\n", str);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
+static res_T
+parse_mesh_type(const char* str, enum sln_mesh_type* type)
+{
+ res_T res = RES_OK;
+ ASSERT(str && type);
+
+ if(!strcmp(str, "fit")) {
+ *type = SLN_MESH_FIT;
+ } else if(!strcmp(str, "upper")) {
+ *type = SLN_MESH_UPPER;
+ } else {
+ fprintf(stderr, "invalid mesh type `%s'\n", str);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
+static res_T
+parse_polyline_opt(const char* str, void* ptr)
+{
+ enum { ERR, MESH, VCOUNT } opt;
+ char buf[BUFSIZ];
+
+ struct args* args = ptr;
+
+ char* key = NULL;
+ char* val = NULL;
+ char* tk_ctx = NULL;
+ res_T res = RES_OK;
+
+ ASSERT(str && ptr);
+
+ if(strlen(str) >= sizeof(buf) -1/*NULL char*/) {
+ fprintf(stderr, "could not duplicate polyline option `%s'\n", str);
+ res = RES_MEM_ERR;
+ goto error;
+ }
+
+ strncpy(buf, str, sizeof(buf));
+
+ key = strtok_r(buf, "=", &tk_ctx);
+ val = strtok_r(NULL, "", &tk_ctx);
+
+ if(!strcmp(key, "err")) opt = ERR;
+ else if(!strcmp(key, "mesh")) opt = MESH;
+ else if(!strcmp(key, "vcount")) opt = MESH;
+ else {
+ fprintf(stderr, "invalid polyline option `%s'\n", key);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ switch(opt) {
+ case ERR:
+ res = cstr_to_double(val, &args->mesh_decimation_err);
+ if(res == RES_OK && args->mesh_decimation_err < 0) res = RES_BAD_ARG;
+ break;
+ case MESH:
+ res = parse_mesh_type(val, &args->mesh_type);
+ break;
+ case VCOUNT:
+ res = cstr_to_uint(val, &args->nvertices_hint);
+ break;
+ default: FATAL("Unreachable code\n"); break;
+ }
+
+ if(res != RES_OK) {
+ fprintf(stderr,
+ "error while parsing the polyline option `%s' -- %s\n",
+ str, res_to_cstr(res));
+ goto error;
+ }
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
+static res_T
+args_init(struct args* args, int argc, char** argv)
+{
+ int opt = 0;
+ res_T res = RES_OK;
+
+ ASSERT(args);
+
+ *args = ARGS_DEFAULT;
+
+ while((opt = getopt(argc, argv, "e:hl:o:P:sT:m:vx:")) != -1) {
+ switch(opt) {
+ case 'e':
+ res = cstr_parse_list(optarg, ':', parse_polyline_opt, args);
+ break;
+ case 'h':
+ usage(stdout);
+ args->quit = 1;
+ goto exit;
+ case 'l':
+ res = parse_line_profile(optarg, &args->line_profile);
+ break;
+ case 'o': args->output = optarg; break;
+ case 'P':
+ res = cstr_to_double(optarg, &args->pressure);
+ if(res == RES_OK && args->pressure < 0) res = RES_BAD_ARG;
+ break;
+ case 's': args->line_format = LINE_LIST_SHTR; break;
+ case 'T':
+ res = cstr_to_double(optarg, &args->temperature);
+ if(res == RES_OK && args->temperature < 0) res = RES_BAD_ARG;
+ break;
+ case 'm': args->molparams = optarg; break;
+ case 'v': args->verbose += (args->verbose < 3); break;
+ case 'x': args->mixture = optarg; break;
+ default: res = RES_BAD_ARG; break;
+ }
+
+ if(res != RES_OK) {
+ if(optarg) {
+ fprintf(stderr, "%s: invalid option argument '%s' -- '%c'\n",
+ argv[0], optarg, opt);
+ }
+ goto error;
+ }
+ }
+
+ #define MANDATORY(Cond, Name, Opt) { \
+ if(!(Cond)) { \
+ fprintf(stderr, "%s: %s missing -- option '-%c'\n", argv[0], (Name), (Opt)); \
+ res = RES_BAD_ARG; \
+ goto error; \
+ } \
+ } (void)0
+ MANDATORY(args->pressure >= 0, "pressure", 'P');
+ MANDATORY(args->temperature >= 0, "temperature", 'T');
+ MANDATORY(args->molparams, "molparams", 'm');
+ MANDATORY(args->mixture, "mixture", 'x');
+ #undef MANDATORY
+
+exit:
+ return res;
+error:
+ usage(stderr);
+ goto exit;
+}
+
+/*******************************************************************************
+ * The program
+ ******************************************************************************/
+int
+main(int argc, char** argv)
+{
+ struct args args = ARGS_DEFAULT;
+ int err = 0;
+ res_T res = RES_OK;
+
+ if((res = args_init(&args, argc, argv)) != RES_OK) goto error;
+ if(args.quit) goto exit;
+
+exit:
+ CHK(mem_allocated_size() == 0);
+ return err;
+error:
+ err = 1;
+ goto exit;
+}