From 310a04f701eb249ba26fc3d069c62d3e46443b5d Mon Sep 17 00:00:00 2001
From: Dimitris Papavasiliou <dpapavas@protonmail.ch>
Date: Tue, 4 Oct 2022 21:33:34 +0300
Subject: [PATCH] Add support for user exported C libraries

This uses the existing mechanism for statically compiled C libraries,
to allow the user to export their own C libraries in a similar way.
User exported libraries can be added on top of statically compiled C
libraries or exist on their own (by setting SEXP_USE_STATIC_LIBS_EMPTY).
---
 eval.c                   | 39 ++++++++++++++++++++++++++++++++++-----
 include/chibi/features.h | 35 +++++++++++++++++++++++++----------
 include/chibi/sexp.h     | 14 ++++++++++++--
 3 files changed, 71 insertions(+), 17 deletions(-)

diff --git a/eval.c b/eval.c
index 9d1d09f2..14224ee9 100644
--- a/eval.c
+++ b/eval.c
@@ -1374,24 +1374,53 @@ sexp sexp_stream_portp_op (sexp ctx, sexp self, sexp_sint_t n, sexp port) {
 #endif
 
 #if SEXP_USE_STATIC_LIBS
-#if SEXP_USE_STATIC_LIBS_NO_INCLUDE
+#if SEXP_USE_STATIC_LIBS_EMPTY
+struct sexp_library_entry_t* sexp_static_libraries = NULL;
+#elif SEXP_USE_STATIC_LIBS_NO_INCLUDE
 extern struct sexp_library_entry_t* sexp_static_libraries;
 #else
 #include "clibs.c"
 #endif
+
+void sexp_add_static_libraries(struct sexp_library_entry_t* libraries)
+{
+  struct sexp_library_entry_t *entry, *table;
+
+  if (!sexp_static_libraries) {
+    sexp_static_libraries = libraries;
+    return;
+  }
+
+  for (table = sexp_static_libraries; ;
+       table = (struct sexp_library_entry_t*)entry->init) {
+    for (entry = &table[0]; entry->name; entry++)
+       ;
+    if (!entry->init) {
+      entry->init = (sexp_init_proc)libraries;
+      return;
+    }
+  }
+}
+
 static struct sexp_library_entry_t *sexp_find_static_library(const char *file)
 {
   size_t base_len;
-  struct sexp_library_entry_t *entry;
+  struct sexp_library_entry_t *entry, *table;
 
+  if(!sexp_static_libraries)
+    return NULL;
   if (file[0] == '.' && file[1] == '/')
     file += 2;
   base_len = strlen(file) - strlen(sexp_so_extension);
   if (strcmp(file + base_len, sexp_so_extension))
     return NULL;
-  for (entry = &sexp_static_libraries[0]; entry->name; entry++)
-    if (! strncmp(file, entry->name, base_len))
-      return entry;
+  for (table = sexp_static_libraries;
+       table;
+       table = (struct sexp_library_entry_t*)entry->init) {
+    for (entry = &table[0]; entry->name; entry++)
+      if (! strncmp(file, entry->name, base_len))
+        return entry;
+  }
   return NULL;
 }
 #else
diff --git a/include/chibi/features.h b/include/chibi/features.h
index ff977de7..dd8deb21 100644
--- a/include/chibi/features.h
+++ b/include/chibi/features.h
@@ -23,16 +23,27 @@
 /*   sexp_init_library(ctx, env) function provided. */
 /* #define SEXP_USE_DL 0 */
 
-/* uncomment this to statically compile all C libs */
-/*   If set, this will statically include the clibs.c file */
-/*   into the standard environment, so that you can have */
-/*   access to a predefined set of C libraries without */
-/*   needing dynamic loading.  The clibs.c file is generated */
-/*   automatically by searching the lib directory for */
-/*   modules with include-shared, but can be hand-tailored */
-/*   to your needs. */
+/* uncomment this to support statically compiled C libs */
+/*   Unless SEXP_USE_STATIC_LIBS_EMPTY is set (see below), this */
+/*   will statically include the clibs.c file into the standard */
+/*   environment, so that you can have access to a predefined set */
+/*   of C libraries without needing dynamic loading.  The clibs.c */
+/*   file is generated automatically by searching the lib directory */
+/*   for modules with include-shared, but can be hand-tailored to */
+/*   your needs.  You can also register your own C libraries using */
+/*   sexp_add_static_libraries (see below). */
 /* #define SEXP_USE_STATIC_LIBS 1 */
 
+/* uncomment this to enable user exported C libs */
+/*   You can register your own C libraries using */
+/*   sexp_add_static_libraries.  Each entry in the supplied table, */
+/*   is a name/entry point pair.  These work as if they were */
+/*   dynamically loaded libraries, so naming follows the same */
+/*   conventions.  An entry {"foo", init_foo} will register a */
+/*   library that can be loaded with (load "foo"), or */
+/*   (include-shared "foo"), both of which will call init_foo. */
+/* #define SEXP_USE_STATIC_LIBS_EMPTY 1 */
+
 /* uncomment this to disable detailed source info for debugging */
 /*   By default Chibi will associate source info with every */
 /*   bytecode offset.  By disabling this only lambda-level source */
@@ -461,13 +472,17 @@
 #endif
 #endif
 
+#ifndef SEXP_USE_STATIC_LIBS_EMPTY
+#define SEXP_USE_STATIC_LIBS_EMPTY 0
+#endif
+
 #ifndef SEXP_USE_STATIC_LIBS
-#define SEXP_USE_STATIC_LIBS 0
+#define SEXP_USE_STATIC_LIBS SEXP_USE_STATIC_LIBS_EMPTY
 #endif
 
 /* don't include clibs.c - include separately or link */
 #ifndef SEXP_USE_STATIC_LIBS_NO_INCLUDE
-#ifdef PLAN9
+#if defined(PLAN9) || SEXP_USE_STATIC_LIBS_EMPTY
 #define SEXP_USE_STATIC_LIBS_NO_INCLUDE 0
 #else
 #define SEXP_USE_STATIC_LIBS_NO_INCLUDE 1
diff --git a/include/chibi/sexp.h b/include/chibi/sexp.h
index 3ff3b53b..5ae0eb35 100644
--- a/include/chibi/sexp.h
+++ b/include/chibi/sexp.h
@@ -395,8 +395,8 @@ struct sexp_gc_var_t {
   struct sexp_gc_var_t *next;
 };
 
-struct sexp_library_entry_t {   /* for static builds */
-  const char *name;
+struct sexp_library_entry_t {   /* for static builds and user exported C */
+  const char *name;             /* libaries */
   sexp_init_proc init;
 };
 
@@ -1680,6 +1680,16 @@ sexp sexp_finalize_dl (sexp ctx, sexp self, sexp_sint_t n, sexp dl);
 #define sexp_current_source_param
 #endif
 
+/* To export a library from the embedding C program to Scheme, so    */
+/* that it can be included into Scheme library foo/qux.sld as        */
+/* (include-shared "bar"), libraries should contain the entry        */
+/* {"foo/bar", init_bar}.  The signature and function of init_bar is */
+/* the same as that of sexp_init_library in shared libraries.  The   */
+/* array libraries must be terminated with {NULL, NULL} and must     */
+/* remain valid throughout its use by Chibi.                         */
+
+SEXP_API void sexp_add_static_libraries(struct sexp_library_entry_t* libraries);
+
 SEXP_API sexp sexp_alloc_tagged_aux(sexp ctx, size_t size, sexp_uint_t tag sexp_current_source_param);
 SEXP_API sexp sexp_make_context(sexp ctx, size_t size, size_t max_size);
 SEXP_API sexp sexp_cons_op(sexp ctx, sexp self, sexp_sint_t n, sexp head, sexp tail);