2 Commits cd4e739fb7 ... 350831b20f

Author SHA1 Message Date
  Legimet 350831b20f Node-like module system and other minor changes 6 years ago
  Legimet 9cf79c429b Add misc.h 6 years ago
9 changed files with 526 additions and 78 deletions
  1. 3 2
      Makefile
  2. 333 0
      duk_module_node.c
  3. 9 0
      duk_module_node.h
  4. 27 28
      main.c
  5. 13 7
      misc.c
  6. 27 0
      misc.h
  7. 111 38
      module.c
  8. 3 2
      module.h
  9. 0 1
      nsp_texture.c

+ 3 - 2
Makefile

@@ -6,8 +6,9 @@ GENZEHN = genzehn
 
 BUILD_DATE = $(shell date '+%Y-%m-%d')
 VERSION = $(shell git describe --always --dirty 2> /dev/null || echo $(VERSION_SHORT))
-GCCFLAGS = -std=gnu11 -Wall -W -marm -fstrict-aliasing -fomit-frame-pointer -ffunction-sections -fdata-sections \
-	   -DBUILD_DATE=\"$(BUILD_DATE)\" -DVERSION=\"$(VERSION)\" -DOLD_SCREEN_API
+GCCFLAGS = -std=gnu11 -Wall -Wextra -marm -fstrict-aliasing -fomit-frame-pointer -ffunction-sections -fdata-sections \
+	   -DBUILD_DATE=\"$(BUILD_DATE)\" -DVERSION=\"$(VERSION)\" -DOLD_SCREEN_API -Wno-implicit-fallthrough \
+	   -Wno-format-truncation
 LDFLAGS = -Wl,--gc-sections,--nspireio
 ZEHNFLAGS = --name "Duktape-nspire" --author "Legimet" --notice "JavaScript (ECMAScript) interpreter using Duktape"
 

+ 333 - 0
duk_module_node.c

@@ -0,0 +1,333 @@
+/*
+ *  Node.js-like module loading framework for Duktape
+ *
+ *  https://nodejs.org/api/modules.html
+ */
+
+#include "duktape.h"
+#include "duk_module_node.h"
+
+#if DUK_VERSION >= 19999
+static duk_int_t duk__eval_module_source(duk_context *ctx, void *udata);
+#else
+static duk_int_t duk__eval_module_source(duk_context *ctx);
+#endif
+static void duk__push_module_object(duk_context *ctx, const char *id, duk_bool_t main);
+
+static duk_bool_t duk__get_cached_module(duk_context *ctx, const char *id) {
+	duk_push_global_stash(ctx);
+	(void) duk_get_prop_string(ctx, -1, "\xff" "requireCache");
+	if (duk_get_prop_string(ctx, -1, id)) {
+		duk_remove(ctx, -2);
+		duk_remove(ctx, -2);
+		return 1;
+	} else {
+		duk_pop_3(ctx);
+		return 0;
+	}
+}
+
+/* Place a `module` object on the top of the value stack into the require cache
+ * based on its `.id` property.  As a convenience to the caller, leave the
+ * object on top of the value stack afterwards.
+ */
+static void duk__put_cached_module(duk_context *ctx) {
+	/* [ ... module ] */
+
+	duk_push_global_stash(ctx);
+	(void) duk_get_prop_string(ctx, -1, "\xff" "requireCache");
+	duk_dup(ctx, -3);
+
+	/* [ ... module stash req_cache module ] */
+
+	(void) duk_get_prop_string(ctx, -1, "id");
+	duk_dup(ctx, -2);
+	duk_put_prop(ctx, -4);
+
+	duk_pop_3(ctx);  /* [ ... module ] */
+}
+
+static void duk__del_cached_module(duk_context *ctx, const char *id) {
+	duk_push_global_stash(ctx);
+	(void) duk_get_prop_string(ctx, -1, "\xff" "requireCache");
+	duk_del_prop_string(ctx, -1, id);
+	duk_pop_2(ctx);
+}
+
+static duk_ret_t duk__handle_require(duk_context *ctx) {
+	/*
+	 *  Value stack handling here is a bit sloppy but should be correct.
+	 *  Call handling will clean up any extra garbage for us.
+	 */
+
+	const char *id;
+	const char *parent_id;
+	duk_idx_t module_idx;
+	duk_idx_t stash_idx;
+	duk_int_t ret;
+
+	duk_push_global_stash(ctx);
+	stash_idx = duk_normalize_index(ctx, -1);
+
+	duk_push_current_function(ctx);
+	(void) duk_get_prop_string(ctx, -1, "\xff" "moduleId");
+	parent_id = duk_require_string(ctx, -1);
+	(void) parent_id;  /* not used directly; suppress warning */
+
+	/* [ id stash require parent_id ] */
+
+	id = duk_require_string(ctx, 0);
+
+	(void) duk_get_prop_string(ctx, stash_idx, "\xff" "modResolve");
+	duk_dup(ctx, 0);   /* module ID */
+	duk_dup(ctx, -3);  /* parent ID */
+	duk_call(ctx, 2);
+
+	/* [ ... stash ... resolved_id ] */
+
+	id = duk_require_string(ctx, -1);
+
+	if (duk__get_cached_module(ctx, id)) {
+		goto have_module;  /* use the cached module */
+	}
+
+	duk__push_module_object(ctx, id, 0 /*main*/);
+	duk__put_cached_module(ctx);  /* module remains on stack */
+
+	/*
+	 *  From here on out, we have to be careful not to throw.  If it can't be
+	 *  avoided, the error must be caught and the module removed from the
+	 *  require cache before rethrowing.  This allows the application to
+	 *  reattempt loading the module.
+	 */
+
+	module_idx = duk_normalize_index(ctx, -1);
+
+	/* [ ... stash ... resolved_id module ] */
+
+	(void) duk_get_prop_string(ctx, stash_idx, "\xff" "modLoad");
+	duk_dup(ctx, -3);  /* resolved ID */
+	(void) duk_get_prop_string(ctx, module_idx, "exports");
+	duk_dup(ctx, module_idx);
+	ret = duk_pcall(ctx, 3);
+	if (ret != DUK_EXEC_SUCCESS) {
+		duk__del_cached_module(ctx, id);
+		(void) duk_throw(ctx);  /* rethrow */
+	}
+
+	if (duk_is_string(ctx, -1)) {
+		duk_int_t ret;
+
+		/* [ ... module source ] */
+
+#if DUK_VERSION >= 19999
+		ret = duk_safe_call(ctx, duk__eval_module_source, NULL, 2, 1);
+#else
+		ret = duk_safe_call(ctx, duk__eval_module_source, 2, 1);
+#endif
+		if (ret != DUK_EXEC_SUCCESS) {
+			duk__del_cached_module(ctx, id);
+			(void) duk_throw(ctx);  /* rethrow */
+		}
+	} else if (duk_is_undefined(ctx, -1)) {
+		duk_pop(ctx);
+	} else {
+		duk__del_cached_module(ctx, id);
+		(void) duk_error(ctx, DUK_ERR_TYPE_ERROR, "invalid module load callback return value");
+	}
+
+	/* fall through */
+
+ have_module:
+	/* [ ... module ] */
+
+	(void) duk_get_prop_string(ctx, -1, "exports");
+	return 1;
+}
+
+static void duk__push_require_function(duk_context *ctx, const char *id) {
+	duk_push_c_function(ctx, duk__handle_require, 1);
+	duk_push_string(ctx, "name");
+	duk_push_string(ctx, "require");
+	duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE);
+	duk_push_string(ctx, id);
+	duk_put_prop_string(ctx, -2, "\xff" "moduleId");
+
+	/* require.cache */
+	duk_push_global_stash(ctx);
+	(void) duk_get_prop_string(ctx, -1, "\xff" "requireCache");
+	duk_put_prop_string(ctx, -3, "cache");
+	duk_pop(ctx);
+
+	/* require.main */
+	duk_push_global_stash(ctx);
+	(void) duk_get_prop_string(ctx, -1, "\xff" "mainModule");
+	duk_put_prop_string(ctx, -3, "main");
+	duk_pop(ctx);
+}
+
+static void duk__push_module_object(duk_context *ctx, const char *id, duk_bool_t main) {
+	duk_push_object(ctx);
+
+	/* Set this as the main module, if requested */
+	if (main) {
+		duk_push_global_stash(ctx);
+		duk_dup(ctx, -2);
+		duk_put_prop_string(ctx, -2, "\xff" "mainModule");
+		duk_pop(ctx);
+	}
+
+	/* Node.js uses the canonicalized filename of a module for both module.id
+	 * and module.filename.  We have no concept of a file system here, so just
+	 * use the module ID for both values.
+	 */
+	duk_push_string(ctx, id);
+	duk_dup(ctx, -1);
+	duk_put_prop_string(ctx, -3, "filename");
+	duk_put_prop_string(ctx, -2, "id");
+
+	/* module.exports = {} */
+	duk_push_object(ctx);
+	duk_put_prop_string(ctx, -2, "exports");
+
+	/* module.loaded = false */
+	duk_push_false(ctx);
+	duk_put_prop_string(ctx, -2, "loaded");
+
+	/* module.require */
+	duk__push_require_function(ctx, id);
+	duk_put_prop_string(ctx, -2, "require");
+}
+
+#if DUK_VERSION >= 19999
+static duk_int_t duk__eval_module_source(duk_context *ctx, void *udata) {
+#else
+static duk_int_t duk__eval_module_source(duk_context *ctx) {
+#endif
+	const char *src;
+
+	/*
+	 *  Stack: [ ... module source ]
+	 */
+
+#if DUK_VERSION >= 19999
+	(void) udata;
+#endif
+
+	/* Wrap the module code in a function expression.  This is the simplest
+	 * way to implement CommonJS closure semantics and matches the behavior of
+	 * e.g. Node.js.
+	 */
+	duk_push_string(ctx, "(function(exports,require,module,__filename,__dirname){");
+	src = duk_require_string(ctx, -2);
+	duk_push_string(ctx, (src[0] == '#' && src[1] == '!') ? "//" : "");  /* Shebang support. */
+	duk_dup(ctx, -3);  /* source */
+	duk_push_string(ctx, "\n})");  /* Newline allows module last line to contain a // comment. */
+	duk_concat(ctx, 4);
+
+	/* [ ... module source func_src ] */
+
+	(void) duk_get_prop_string(ctx, -3, "filename");
+	duk_compile(ctx, DUK_COMPILE_EVAL);
+	duk_call(ctx, 0);
+
+	/* [ ... module source func ] */
+
+	/* Set name for the wrapper function. */
+	duk_push_string(ctx, "name");
+	duk_push_string(ctx, "main");
+	duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_FORCE);
+
+	/* call the function wrapper */
+	(void) duk_get_prop_string(ctx, -3, "exports");   /* exports */
+	(void) duk_get_prop_string(ctx, -4, "require");   /* require */
+	duk_dup(ctx, -5);                                 /* module */
+	(void) duk_get_prop_string(ctx, -6, "filename");  /* __filename */
+	duk_push_undefined(ctx);                          /* __dirname */
+	duk_call(ctx, 5);
+
+	/* [ ... module source result(ignore) ] */
+
+	/* module.loaded = true */
+	duk_push_true(ctx);
+	duk_put_prop_string(ctx, -4, "loaded");
+
+	/* [ ... module source retval ] */
+
+	duk_pop_2(ctx);
+
+	/* [ ... module ] */
+
+	return 1;
+}
+
+/* Load a module as the 'main' module. */
+duk_ret_t duk_module_node_peval_main(duk_context *ctx, const char *path) {
+	/*
+	 *  Stack: [ ... source ]
+	 */
+
+	duk__push_module_object(ctx, path, 1 /*main*/);
+	/* [ ... source module ] */
+
+	duk_dup(ctx, 0);
+	/* [ ... source module source ] */
+
+#if DUK_VERSION >= 19999
+	return duk_safe_call(ctx, duk__eval_module_source, NULL, 2, 1);
+#else
+	return duk_safe_call(ctx, duk__eval_module_source, 2, 1);
+#endif
+}
+
+void duk_module_node_init(duk_context *ctx) {
+	/*
+	 *  Stack: [ ... options ] => [ ... ]
+	 */
+
+	duk_idx_t options_idx;
+
+	duk_require_object_coercible(ctx, -1);  /* error before setting up requireCache */
+	options_idx = duk_require_normalize_index(ctx, -1);
+
+	/* Initialize the require cache to a fresh object. */
+	duk_push_global_stash(ctx);
+#if DUK_VERSION >= 19999
+	duk_push_bare_object(ctx);
+#else
+	duk_push_object(ctx);
+	duk_push_undefined(ctx);
+	duk_set_prototype(ctx, -2);
+#endif
+	duk_put_prop_string(ctx, -2, "\xff" "requireCache");
+	duk_pop(ctx);
+
+	/* Stash callbacks for later use.  User code can overwrite them later
+	 * on directly by accessing the global stash.
+	 */
+	duk_push_global_stash(ctx);
+	duk_get_prop_string(ctx, options_idx, "resolve");
+	duk_require_function(ctx, -1);
+	duk_put_prop_string(ctx, -2, "\xff" "modResolve");
+	duk_get_prop_string(ctx, options_idx, "load");
+	duk_require_function(ctx, -1);
+	duk_put_prop_string(ctx, -2, "\xff" "modLoad");
+	duk_pop(ctx);
+
+	/* Stash main module. */
+	duk_push_global_stash(ctx);
+	duk_push_undefined(ctx);
+	duk_put_prop_string(ctx, -2, "\xff" "mainModule");
+	duk_pop(ctx);
+
+	/* register `require` as a global function. */
+	duk_push_global_object(ctx);
+	duk_push_string(ctx, "require");
+	duk__push_require_function(ctx, "");
+	duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE |
+	                      DUK_DEFPROP_SET_WRITABLE |
+	                      DUK_DEFPROP_SET_CONFIGURABLE);
+	duk_pop(ctx);
+
+	duk_pop(ctx);  /* pop argument */
+}

+ 9 - 0
duk_module_node.h

@@ -0,0 +1,9 @@
+#if !defined(DUK_MODULE_NODE_H_INCLUDED)
+#define DUK_MODULE_NODE_H_INCLUDED
+
+#include "duktape.h"
+
+extern duk_ret_t duk_module_node_peval_main(duk_context *ctx, const char *path);
+extern void duk_module_node_init(duk_context *ctx);
+
+#endif  /* DUK_MODULE_NODE_H_INCLUDED */

+ 27 - 28
main.c

@@ -17,6 +17,7 @@
 // along with Duktape-nspire. If not, see <http://www.gnu.org/licenses/>.
 
 #include "duktape.h"
+#include "duk_module_node.h"
 #include "duk_print_alert.h"
 #include "module.h"
 #include "misc.h"
@@ -29,37 +30,28 @@ duk_context *ctx;
 
 // Push stack of Error object at -1, remove the Error object
 duk_ret_t get_error_stack(duk_context *ctx, __attribute__((unused)) void *udata) {
-	if (duk_is_error(ctx, -1) && duk_has_prop_string(ctx, -1, "stack")) {
-		duk_get_prop_string(ctx, -1, "stack");
+	if (duk_is_error(ctx, 0) && duk_has_prop_string(ctx, 0, "stack")) {
+		duk_get_prop_string(ctx, 0, "stack");
 		duk_remove(ctx, -2);
 	}
 	return 1;
 }
 
-// Print stack of Error, remove the Error object
-void print_pop_error(void) {
+// Print stack of Error
+void print_error(void) {
 	duk_safe_call(ctx, get_error_stack, NULL, 1, 1);
 	fprintf(stderr, "%s\n", duk_safe_to_string(ctx, -1));
 	duk_pop(ctx);
 }
 
-// Compile JS file
-duk_ret_t compile_file(duk_context *ctx, void *filename) {
-	push_file_contents(ctx, filename);
-	duk_push_string(ctx, filename);
-	duk_compile(ctx, DUK_COMPILE_SHEBANG);
-	return 1;
-}
-
 // Run JS file
 int handle_file(char *path) {
-	if (duk_safe_call(ctx, compile_file, path, 0, 1)) {
-		print_pop_error();
+	if (push_file_contents_safe(ctx, path)) {
+		print_error();
 		return 1;
 	}
-	duk_push_global_object(ctx);
-	if (duk_pcall_method(ctx, 0)) {
-		print_pop_error();
+	if (duk_module_node_peval_main(ctx, path)) {
+		print_error();
 		return 1;
 	}
 	return 0;
@@ -69,7 +61,8 @@ int handle_file(char *path) {
 int handle_repl(void) {
 	char input[256];
 
-	puts("Duktape-nspire " VERSION "\nBuilt on " BUILD_DATE ", using Duktape " DUK_GIT_DESCRIBE);
+	puts("Duktape-nspire " VERSION "\n"
+	     "Built on " BUILD_DATE " with Duktape " DUK_GIT_DESCRIBE);
 
 	while (true) {
 		printf("> ");
@@ -86,12 +79,12 @@ int handle_repl(void) {
 			return 0;
 		}
 		if (duk_pcompile_string(ctx, 0, input)) {
-			print_pop_error();
+			print_error();
 			continue;
 		}
 		duk_push_global_object(ctx);
 		if (duk_pcall_method(ctx, 0)) {
-			print_pop_error();
+			print_error();
 			continue;
 		}
 		puts(duk_safe_to_string(ctx, -1));
@@ -106,8 +99,13 @@ void cleanup(void) {
 
 void fatal_error_handler(__attribute__((unused)) void *udata, const char *msg) {
 	fputs("*** FATAL ERROR", stderr);
-	if (msg) fputs(msg, stderr);
+	if (msg) {
+		fputs(": ", stderr);
+		fputs(msg, stderr);
+	}
 	fputc('\n', stderr);
+	wait_no_key_pressed();
+	abort();
 }
 
 int main(int argc, char **argv) {
@@ -133,14 +131,15 @@ int main(int argc, char **argv) {
 		return EXIT_FAILURE;
 	}
 
-	duk_print_alert_init(ctx, 0); // Needed for print/alert
+	// Module system - register the resolve and load functions
+	duk_push_object(ctx);
+	duk_push_c_function(ctx, cb_resolve_module, DUK_VARARGS);
+	duk_put_prop_string(ctx, -2, "resolve");
+	duk_push_c_function(ctx, cb_load_module, DUK_VARARGS);
+	duk_put_prop_string(ctx, -2, "load");
+	duk_module_node_init(ctx);
 
-	// Add modSearch function for module loading support
-	duk_push_global_object(ctx);
-	duk_get_prop_string(ctx, -1, "Duktape");
-	duk_push_c_function(ctx, module_search, 4);
-	duk_put_prop_string(ctx, -2, "modSearch");
-	duk_pop_2(ctx);
+	duk_print_alert_init(ctx, 0); // Needed for print/alert
 
 	int ret = EXIT_SUCCESS;
 	if (argc <= 1) {

+ 13 - 7
misc.c

@@ -20,11 +20,8 @@
 #include <errno.h>
 #include <string.h>
 #include <stdio.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
 
-duk_ret_t push_file_contents(duk_context *ctx, void *filename) {
+void push_file_contents(duk_context *ctx, const char *filename) {
 	FILE *file = fopen(filename, "rb");
 	if (!file) goto error;
 	if (fseek(file, 0, SEEK_END)) goto error;
@@ -33,7 +30,7 @@ duk_ret_t push_file_contents(duk_context *ctx, void *filename) {
 	if (size == 0) {
 		fclose(file);
 		duk_push_lstring(ctx, NULL, 0);
-		return 1;
+		return;
 	}
 	if (fseek(file, 0, SEEK_SET)) goto error;
 	char *buf = duk_push_fixed_buffer(ctx, size);
@@ -43,10 +40,19 @@ duk_ret_t push_file_contents(duk_context *ctx, void *filename) {
 	}
 	fclose(file);
 	duk_buffer_to_string(ctx, -1);
-	return 1;
+	return;
 
 error:
 	if (file)
 		fclose(file);
-	return duk_error(ctx, DUK_ERR_TYPE_ERROR, "Unable to read %s: %s", filename, strerror(errno));
+	(void) duk_error(ctx, DUK_ERR_TYPE_ERROR, "Unable to read '%s': %s", filename, strerror(errno));
+}
+
+static duk_ret_t push_file_contents_wrapper(duk_context *ctx, void *filename) {
+	push_file_contents(ctx, filename);
+	return 1;
+}
+
+duk_ret_t push_file_contents_safe(duk_context *ctx, char *filename) {
+	return duk_safe_call(ctx, push_file_contents_wrapper, filename, 0, 1);
 }

+ 27 - 0
misc.h

@@ -0,0 +1,27 @@
+// Miscellaneous functions
+// Copyright (C) 2015 Legimet
+//
+// This file is part of Duktape-nspire.
+//
+// Duktape-nspire 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.
+//
+// Duktape-nspire 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with Duktape-nspire. If not, see <http://www.gnu.org/licenses/>.
+
+#ifndef MISC_H
+#define MISC_H
+
+#include "duktape.h"
+
+void push_file_contents(duk_context *ctx, const char *filename);
+duk_ret_t push_file_contents_safe(duk_context *ctx, char *filename);
+
+#endif

+ 111 - 38
module.c

@@ -18,52 +18,125 @@
 
 #include "duktape.h"
 #include "module.h"
+#include "misc.h"
 #include <stdbool.h>
 #include <stdio.h>
-#include <errno.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
 
-// Duktape.modSearch function, needed for loading modules with require()
-duk_ret_t module_search(duk_context *ctx) {
-	const char *id = duk_require_string(ctx, 0);
+duk_ret_t cb_resolve_module(duk_context *ctx) {
+	/*
+	 *  Entry stack: [ requested_id parent_id ]
+	 */
 
-	// C modules: add functions to exports variable (3rd argument) and return undefined
-	for (int i = 0; i < c_module_count; i++) {
-		if (!strcmp(c_module_list[i].name, id)) {
-			duk_push_c_function(ctx, c_module_list[i].init_func, 0);
-			duk_call(ctx, 0);
+	static const char * start[] = {"/", "./", "../"};
+	static const char * ext[] = {".tns", ".js.tns", ".json.tns",
+		"/index.js.tns", "/index.json.tns"};
+
+	const char *requested_id = duk_get_string(ctx, 0);
+	const char *parent_id = duk_get_string(ctx, 1);  /* calling module */
+
+	char resolved_id[PATH_MAX];
+
+	// Check if it is a built-in C module
+	for (unsigned i = 0; i < c_module_count; ++i) {
+		if (!strcmp(c_module_list[i].name, requested_id)) {
+			strcpy(resolved_id, requested_id);
+			goto success;
+		}
+	}
+
+	// External modules
+	bool valid = false;
+	for (unsigned i = 0; i < sizeof(start) / sizeof(start[0]); ++i) {
+		if (!strncmp(requested_id, start[i], strlen(start[i]))) {
+			valid = true;
+			break;
+		}
+	}
+
+	if (!valid) goto failure;
+
+	char path[PATH_MAX];
+	char *cur;
+	struct stat stbuf;
+
+	if (requested_id[0] == '/') { // Absolute path
+		path[0] = '\0';
+		cur = path;
+	} else { // Relative path
+		strcpy(path, parent_id);
+		cur = strrchr(path, '/'); // Get rid of basename
+		if (cur) ++cur;
+		else cur = path;
+	}
+
+	size_t remaining = PATH_MAX - (cur - path);
+	size_t copied;
+	if ((copied = strlcpy(cur, requested_id, remaining)) >= remaining) goto failure;
+	remaining -= copied;
+	cur += copied;
+
+	// Check each of the extensions in order
+	for (unsigned i = 0; i < sizeof(ext) / sizeof(ext[0]); ++i) {
+		if (strlcpy(cur, ext[i], remaining) >= remaining)
+			goto failure;
+		if (!stat(path, &stbuf) && S_ISREG(stbuf.st_mode)) {
+			if (realpath(path, resolved_id))
+				goto success;
+		}
+	}
+
+failure:
+	return duk_generic_error(ctx, "Could not find module '%s'", requested_id);
+
+success:
+	duk_push_string(ctx, resolved_id);
+	return 1;  /*nrets*/
+
+}
+
+duk_ret_t cb_load_module(duk_context *ctx) {
+	/*
+	 *  Entry stack: [ resolved_id exports module ]
+	 */
+
+	const char *id = duk_get_string(ctx, 0); // Get the resolved id
+
+	// C modules have ids that don't start with /
+	if (id[0] != '/') {
+		for (unsigned i = 0; i < c_module_count; i++) {
+			if (!strcmp(c_module_list[i].name, id)) {
+				// Call init function, enumerate properties of returned object,
+				// and put them in exports
+				c_module_list[i].init_func(ctx);
+				duk_enum(ctx, -1, 0);
+				while (duk_next(ctx, -1, 1)) {
+					duk_put_prop(ctx, 1);
+				}
+				duk_pop_2(ctx);
+				return 0;
+			}
+		}
+		// We shouldn't reach here!
+		return duk_error(ctx, DUK_ERR_ERROR, "Could not find module '%s'", id);
+	} else { // Otherwise, it's in an external file
+		push_file_contents(ctx, id);
+		size_t len = strlen(id);
+		size_t jsonext_len = strlen(".json.tns");
+		if (len >= jsonext_len && !strcmp(".json.tns", id + len - jsonext_len)) {
+			// Decode JSON file and place in exports
+			duk_json_decode(ctx, -1);
 			duk_enum(ctx, -1, 0);
-			while(duk_next(ctx, -1, 1)) {
-				duk_put_prop(ctx, 2);
+			while (duk_next(ctx, -1, 1)) {
+				duk_put_prop(ctx, 1);
 			}
 			duk_pop_2(ctx);
 			return 0;
+		} else { // A regular JS file
+			return 1;
 		}
 	}
-
-	// JS modules: return source code as a string
-	// Read from file "modname.js.tns"
-	int module_filename_len = strlen(id) + strlen(".js.tns") + 1;
-	char *module_filename = malloc(module_filename_len);
-	if (!module_filename) goto error;
-	snprintf(module_filename, module_filename_len, "%s.js.tns", id);
-	FILE *module_file = fopen(module_filename, "r");
-	free(module_filename);
-	if (!module_file) goto error;
-	if (fseek(module_file, 0, SEEK_END) != 0) goto error;
-	long module_file_size = ftell(module_file);
-	if (module_file_size == -1) goto error;
-	rewind(module_file);
-	char *src = malloc(module_file_size);
-	if (!src) goto error;
-	fread(src, 1, module_file_size, module_file);
-	if (ferror(module_file)) goto error;
-	fclose(module_file);
-
-	duk_push_lstring(ctx, src, module_file_size);
-	free(src);
-
-	return 1;
-
-error:
-	return duk_error(ctx, DUK_ERR_ERROR, "module %s not found: %s", id, strerror(errno));
 }

+ 3 - 2
module.h

@@ -35,8 +35,9 @@ static const struct c_module c_module_list[] = {
 	{"nsp/texture", dukopen_nsp_texture}
 };
 
-static const int c_module_count = (sizeof(c_module_list) / sizeof(c_module_list[0]));
+static const unsigned c_module_count = (sizeof(c_module_list) / sizeof(c_module_list[0]));
 
-duk_ret_t module_search(duk_context *ctx);
+duk_ret_t cb_resolve_module(duk_context *ctx);
+duk_ret_t cb_load_module(duk_context *ctx);
 
 #endif

+ 0 - 1
nsp_texture.c

@@ -17,7 +17,6 @@
 // along with Duktape-nspire. If not, see <http://www.gnu.org/licenses/>.
 
 #include "duktape.h"
-#include <limits.h>
 #include <stdbool.h>
 #include <libndls.h>