Skip to content

Commit 62493a3

Browse files
gh-145: EXTEND exposes module-restricted operators through aliases.
1 parent 4dd81a6 commit 62493a3

1 file changed

Lines changed: 104 additions & 16 deletions

File tree

src/extensions.c

Lines changed: 104 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,18 @@ typedef struct ExtensionExposure {
2525
struct ExtensionExposure* next;
2626
} ExtensionExposure;
2727

28+
typedef struct RegisteredOperator {
29+
char* name;
30+
prefix_operator_fn fn;
31+
int flags;
32+
struct RegisteredOperator* next;
33+
} RegisteredOperator;
34+
2835
typedef struct LoadedExtension {
2936
char* canonical_path;
3037
DynLibHandle handle;
3138
prefix_extension_init_fn init_fn;
39+
RegisteredOperator* ops;
3240
ExtensionExposure* exposures;
3341
struct LoadedExtension* next;
3442
} LoadedExtension;
@@ -38,6 +46,7 @@ static char* g_interpreter_dir = NULL;
3846
static char* g_cwd_dir = NULL;
3947
static const char* g_loading_extension_name = NULL;
4048
static const char* g_loading_scope_name = NULL;
49+
static LoadedExtension* g_current_loading_extension = NULL;
4150

4251
// Registered event handlers and periodic hooks
4352
typedef struct EventHandler {
@@ -383,6 +392,24 @@ static LoadedExtension* loaded_find_by_path(const char* canonical_path) {
383392
static int ctx_register_operator(const char* name, prefix_operator_fn fn, int flags) {
384393
if (!name || name[0] == '\0' || !fn) return -1;
385394

395+
/* Record the operator for the currently-loading extension so we can
396+
expose aliases later when the same extension is imported into other
397+
module scopes without reinitializing the library. */
398+
if (g_current_loading_extension) {
399+
RegisteredOperator* ro = calloc(1, sizeof(RegisteredOperator));
400+
if (ro) {
401+
ro->name = strdup(name);
402+
if (ro->name) {
403+
ro->fn = fn;
404+
ro->flags = flags;
405+
ro->next = g_current_loading_extension->ops;
406+
g_current_loading_extension->ops = ro;
407+
} else {
408+
free(ro);
409+
}
410+
}
411+
}
412+
386413
char* final_name = NULL;
387414
if ((flags & PREFIX_EXTENSION_MODULE_RESTRICTED) != 0 && g_loading_extension_name && g_loading_extension_name[0] != '\0') {
388415
const char* ext_name = g_loading_extension_name;
@@ -528,16 +555,30 @@ static int extension_register_exposure(LoadedExtension* le,
528555
return -1;
529556
}
530557

531-
(void)scope_name;
532-
533-
char* key = exposure_key_for(ext_name);
534-
if (!key) {
558+
char* base_key = exposure_key_for(ext_name);
559+
if (!base_key) {
535560
set_error(error_out, "Out of memory");
536561
return -1;
537562
}
538563

539-
if (exposure_exists(le, key)) {
540-
free(key);
564+
char* scope_key = NULL;
565+
if (scope_name && scope_name[0] != '\0') {
566+
size_t s = strlen(scope_name);
567+
size_t e = strlen(ext_name);
568+
/* use ':' as an internal separator to form a unique exposure key */
569+
scope_key = malloc(s + 1 + e + 1);
570+
if (!scope_key) { free(base_key); set_error(error_out, "Out of memory"); return -1; }
571+
memcpy(scope_key, scope_name, s);
572+
scope_key[s] = ':';
573+
memcpy(scope_key + s + 1, ext_name, e);
574+
scope_key[s + 1 + e] = '\0';
575+
}
576+
577+
int base_exists = exposure_exists(le, base_key);
578+
int scope_exists = scope_key ? exposure_exists(le, scope_key) : 0;
579+
if (base_exists && (!scope_key || scope_exists)) {
580+
free(base_key);
581+
free(scope_key);
541582
return 0;
542583
}
543584

@@ -550,19 +591,58 @@ static int extension_register_exposure(LoadedExtension* le,
550591
ctx.register_event_handler = ctx_register_event_handler;
551592
ctx.register_repl_handler = ctx_register_repl_handler;
552593

553-
g_loading_extension_name = ext_name;
554-
g_loading_scope_name = (scope_name && scope_name[0] != '\0') ? scope_name : NULL;
555-
le->init_fn(&ctx);
556-
g_loading_extension_name = NULL;
557-
g_loading_scope_name = NULL;
594+
/* If this is the first time the extension is exposed (base), run its
595+
init function so it can register operators. Otherwise, the operators
596+
should already be recorded in le->ops and we only need to create
597+
scope-qualified aliases. */
598+
if (!base_exists) {
599+
g_current_loading_extension = le;
600+
g_loading_extension_name = ext_name;
601+
g_loading_scope_name = (scope_name && scope_name[0] != '\0') ? scope_name : NULL;
602+
le->init_fn(&ctx);
603+
g_current_loading_extension = NULL;
604+
g_loading_extension_name = NULL;
605+
g_loading_scope_name = NULL;
606+
607+
if (exposure_add(le, base_key) != 0) {
608+
free(base_key);
609+
free(scope_key);
610+
set_error(error_out, "Out of memory");
611+
return -1;
612+
}
613+
}
558614

559-
if (exposure_add(le, key) != 0) {
560-
free(key);
561-
set_error(error_out, "Out of memory");
562-
return -1;
615+
/* If a scope was provided and we haven't yet exposed the extension under
616+
that scope, create aliases for module-restricted operators. */
617+
if (scope_key && !scope_exists) {
618+
for (RegisteredOperator* ro = le->ops; ro; ro = ro->next) {
619+
if ((ro->flags & PREFIX_EXTENSION_MODULE_RESTRICTED) != 0) {
620+
size_t s = strlen(scope_name);
621+
size_t e = strlen(ext_name);
622+
size_t n = strlen(ro->name);
623+
size_t total = s + 1 + e + 1 + n + 1;
624+
char* alias = malloc(total);
625+
if (!alias) continue;
626+
size_t p = 0;
627+
memcpy(alias + p, scope_name, s); p += s; alias[p++] = '.';
628+
memcpy(alias + p, ext_name, e); p += e; alias[p++] = '.';
629+
memcpy(alias + p, ro->name, n); p += n; alias[p] = '\0';
630+
/* ignore registration errors for aliases */
631+
builtins_register_operator(alias, (BuiltinImplFn)ro->fn, 0, -1, NULL, 0);
632+
free(alias);
633+
}
634+
}
635+
636+
if (exposure_add(le, scope_key) != 0) {
637+
free(base_key);
638+
free(scope_key);
639+
set_error(error_out, "Out of memory");
640+
return -1;
641+
}
563642
}
564643

565-
free(key);
644+
free(base_key);
645+
free(scope_key);
566646
return 0;
567647
}
568648

@@ -704,6 +784,14 @@ void extensions_shutdown(void) {
704784
ex = ex_next;
705785
}
706786

787+
RegisteredOperator* ro = e->ops;
788+
while (ro) {
789+
RegisteredOperator* rn = ro->next;
790+
free(ro->name);
791+
free(ro);
792+
ro = rn;
793+
}
794+
707795
dyn_close_library(e->handle);
708796
free(e->canonical_path);
709797
free(e);

0 commit comments

Comments
 (0)