瀏覽代碼

resource: scan .lua code for requirements

Part-of: #133
Daniele Bartolini 2 年之前
父節點
當前提交
82a3c4f648
共有 5 個文件被更改,包括 237 次插入1 次删除
  1. 1 0
      docs/changelog.rst
  2. 152 0
      src/core/unit_tests.cpp
  3. 79 0
      src/resource/lua_resource.cpp
  4. 4 0
      src/resource/lua_resource.h
  5. 1 1
      src/resource/types.h

+ 1 - 0
docs/changelog.rst

@@ -7,6 +7,7 @@ Changelog
 **Data Compiler**
 **Data Compiler**
 
 
 * Data directories can now be deleted at run-time to force a full data compilation.
 * Data directories can now be deleted at run-time to force a full data compilation.
+* Some dependencies for Lua scripts are now automatically determined by parsing require() calls in the source.
 
 
 **Runtime**
 **Runtime**
 
 

+ 152 - 0
src/core/unit_tests.cpp

@@ -39,6 +39,7 @@
 #include "core/thread/thread.h"
 #include "core/thread/thread.h"
 #include "core/time.h"
 #include "core/time.h"
 #include "core/option.inl"
 #include "core/option.inl"
+#include "resource/lua_resource.h"
 #include <stdlib.h> // EXIT_SUCCESS, EXIT_FAILURE
 #include <stdlib.h> // EXIT_SUCCESS, EXIT_FAILURE
 #include <stdio.h>  // printf
 #include <stdio.h>  // printf
 
 
@@ -1593,6 +1594,156 @@ static void test_option()
 	memory_globals::shutdown();
 	memory_globals::shutdown();
 }
 }
 
 
+static void test_lua_resource()
+{
+#if CROWN_CAN_COMPILE
+	memory_globals::init();
+	{
+		const char *lua = "";
+
+		HashSet<StringView> req(default_allocator());
+		lua_resource_internal::find_requirements(req, lua);
+
+		ENSURE(hash_set::size(req) == 0);
+	}
+	{
+		const char *lua = "require 'foo'";
+
+		HashSet<StringView> req(default_allocator());
+		lua_resource_internal::find_requirements(req, lua);
+
+		ENSURE(hash_set::size(req) == 1);
+		ENSURE(hash_set::has(req, StringView("foo")));
+	}
+	{
+		const char *lua = "require \"foo\"";
+
+		HashSet<StringView> req(default_allocator());
+		lua_resource_internal::find_requirements(req, lua);
+
+		ENSURE(hash_set::size(req) == 1);
+		ENSURE(hash_set::has(req, StringView("foo")));
+	}
+	{
+		const char *lua = "require (\"foo\")";
+
+		HashSet<StringView> req(default_allocator());
+		lua_resource_internal::find_requirements(req, lua);
+
+		ENSURE(hash_set::size(req) == 1);
+		ENSURE(hash_set::has(req, StringView("foo")));
+	}
+	{
+		const char *lua = "require";
+
+		HashSet<StringView> req(default_allocator());
+		lua_resource_internal::find_requirements(req, lua);
+
+		ENSURE(hash_set::size(req) == 0);
+	}
+	{
+		const char *lua = "require '";
+
+		HashSet<StringView> req(default_allocator());
+		lua_resource_internal::find_requirements(req, lua);
+
+		ENSURE(hash_set::size(req) == 0);
+	}
+	{
+		const char *lua = "require ('";
+
+		HashSet<StringView> req(default_allocator());
+		lua_resource_internal::find_requirements(req, lua);
+
+		ENSURE(hash_set::size(req) == 0);
+	}
+	{
+		const char *lua = "-- require (\"foo\")";
+
+		HashSet<StringView> req(default_allocator());
+		lua_resource_internal::find_requirements(req, lua);
+
+		ENSURE(hash_set::size(req) == 0);
+	}
+	{
+		const char *lua = "require (\"foo\") -- comment";
+
+		HashSet<StringView> req(default_allocator());
+		lua_resource_internal::find_requirements(req, lua);
+
+		ENSURE(hash_set::size(req) == 1);
+		ENSURE(hash_set::has(req, StringView("foo")));
+	}
+	{
+		const char *lua = "require --[[ comment --]] (\"foo\")";
+
+		HashSet<StringView> req(default_allocator());
+		lua_resource_internal::find_requirements(req, lua);
+
+		ENSURE(hash_set::size(req) == 1);
+		ENSURE(hash_set::has(req, StringView("foo")));
+	}
+	{
+		const char *lua = "require ( --[[--]] \"foo\" --[[--]])";
+
+		HashSet<StringView> req(default_allocator());
+		lua_resource_internal::find_requirements(req, lua);
+
+		ENSURE(hash_set::size(req) == 1);
+		ENSURE(hash_set::has(req, StringView("foo")));
+	}
+	{
+		const char *lua = "--[[ unmatched multi-line comment";
+
+		HashSet<StringView> req(default_allocator());
+		lua_resource_internal::find_requirements(req, lua);
+
+		ENSURE(hash_set::size(req) == 0);
+	}
+	{
+		const char *lua = "require --[[ unmatched multi-line comment after require";
+
+		HashSet<StringView> req(default_allocator());
+		lua_resource_internal::find_requirements(req, lua);
+
+		ENSURE(hash_set::size(req) == 0);
+	}
+	{
+		const char *lua =
+			"require (\"foo\")"
+			"require 'bar'"
+			"require 'baz'"
+			;
+
+		HashSet<StringView> req(default_allocator());
+		lua_resource_internal::find_requirements(req, lua);
+
+		ENSURE(hash_set::size(req) == 3);
+		ENSURE(hash_set::has(req, StringView("foo")));
+		ENSURE(hash_set::has(req, StringView("bar")));
+		ENSURE(hash_set::has(req, StringView("baz")));
+	}
+	{
+		const char *lua =
+			"function init()"
+			"    require (\"foo\")"
+			"    require 'bar'"
+			"    require 'baz'"
+			"end"
+			;
+
+		HashSet<StringView> req(default_allocator());
+		lua_resource_internal::find_requirements(req, lua);
+
+		ENSURE(hash_set::size(req) == 3);
+		ENSURE(hash_set::has(req, StringView("foo")));
+		ENSURE(hash_set::has(req, StringView("bar")));
+		ENSURE(hash_set::has(req, StringView("baz")));
+	}
+	memory_globals::shutdown();
+#endif // if CROWN_CAN_COMPILE
+}
+
 #define RUN_TEST(name)      \
 #define RUN_TEST(name)      \
 	do {                    \
 	do {                    \
 		printf(#name "\n"); \
 		printf(#name "\n"); \
@@ -1628,6 +1779,7 @@ int main_unit_tests()
 	RUN_TEST(test_process);
 	RUN_TEST(test_process);
 	RUN_TEST(test_filesystem);
 	RUN_TEST(test_filesystem);
 	RUN_TEST(test_option);
 	RUN_TEST(test_option);
+	RUN_TEST(test_lua_resource);
 
 
 	return EXIT_SUCCESS;
 	return EXIT_SUCCESS;
 }
 }

+ 79 - 0
src/resource/lua_resource.cpp

@@ -5,11 +5,13 @@
 
 
 #include "config.h"
 #include "config.h"
 #include "core/containers/array.inl"
 #include "core/containers/array.inl"
+#include "core/containers/hash_set.inl"
 #include "core/filesystem/file.h"
 #include "core/filesystem/file.h"
 #include "core/memory/temp_allocator.inl"
 #include "core/memory/temp_allocator.inl"
 #include "core/process.h"
 #include "core/process.h"
 #include "core/strings/dynamic_string.inl"
 #include "core/strings/dynamic_string.inl"
 #include "core/strings/string_stream.inl"
 #include "core/strings/string_stream.inl"
+#include "core/strings/string_view.inl"
 #include "resource/compile_options.inl"
 #include "resource/compile_options.inl"
 #include "resource/lua_resource.h"
 #include "resource/lua_resource.h"
 
 
@@ -33,6 +35,66 @@ namespace lua_resource
 #if CROWN_CAN_COMPILE
 #if CROWN_CAN_COMPILE
 namespace lua_resource_internal
 namespace lua_resource_internal
 {
 {
+	static const char *skip_blanks(const char *lua)
+	{
+		lua = skip_spaces(lua);
+
+		if (*lua == '-') {
+			++lua;
+			if (*lua++ == '-' && *lua++ == '[' && *lua++ == '[') {
+				// Multi-line comment.
+				const char *mlc_end = strstr(lua, "--]]");
+				if (mlc_end != NULL)
+					lua = mlc_end + 4;
+				else
+					lua += strlen(lua);
+			} else {
+				// Single-line comment.
+				while (*lua && *lua != '\n')
+					++lua;
+			}
+		}
+
+		return skip_spaces(lua);
+	}
+
+	void find_requirements(HashSet<StringView> &requirements, const char *lua)
+	{
+		while (*lua) {
+			lua = skip_blanks(lua);
+
+			if (*lua == 'r') { // Find require()s.
+				const char *require = strstr(lua, "require");
+				if (!require) {
+					++lua;
+					continue;
+				}
+
+				lua = skip_blanks(require + 7);
+
+				if (*lua == '(')
+					++lua;
+
+				lua = skip_blanks(lua);
+
+				if (*lua == '\'' || *lua == '"') {
+					const char *param_begin = lua + 1;
+					const char *param_end   = strchr(param_begin, *lua);
+					if (param_end != NULL) {
+						hash_set::insert(requirements
+							, StringView(param_begin, param_end - param_begin)
+							);
+						lua = param_end + 1;
+					}
+				}
+			} else if (*lua == '-' || isspace(*lua)) {
+				lua = skip_blanks(lua);
+			} else if (*lua) {
+				++lua;
+			}
+		}
+	}
+
 	s32 compile(CompileOptions &opts)
 	s32 compile(CompileOptions &opts)
 	{
 	{
 		TempAllocator1024 ta;
 		TempAllocator1024 ta;
@@ -56,6 +118,23 @@ namespace lua_resource_internal
 			, "Failed to spawn `%s`"
 			, "Failed to spawn `%s`"
 			, argv[0]
 			, argv[0]
 			);
 			);
+
+		// Scan the .lua code for requirements.
+		Buffer lua_code = opts.read();
+		HashSet<StringView> requirements(default_allocator());
+		lua_resource_internal::find_requirements(requirements, array::begin(lua_code));
+
+		auto cur = hash_set::begin(requirements);
+		auto end = hash_set::end(requirements);
+		for (; cur != end; ++cur) {
+			HASH_SET_SKIP_HOLE(requirements, cur);
+
+			TempAllocator256 ta;
+			DynamicString name(ta);
+			name = *cur;
+			opts.add_requirement("lua", name.c_str());
+		}
+
 		StringStream output(ta);
 		StringStream output(ta);
 		opts.read_output(output, pr);
 		opts.read_output(output, pr);
 		s32 ec = pr.wait();
 		s32 ec = pr.wait();

+ 4 - 0
src/resource/lua_resource.h

@@ -21,6 +21,10 @@ struct LuaResource
 
 
 namespace lua_resource_internal
 namespace lua_resource_internal
 {
 {
+	///
+	void find_requirements(HashSet<StringView> &requirements, const char *lua);
+
+	///
 	s32 compile(CompileOptions &opts);
 	s32 compile(CompileOptions &opts);
 
 
 } // namespace lua_resource_internal
 } // namespace lua_resource_internal

+ 1 - 1
src/resource/types.h

@@ -66,7 +66,7 @@ struct UnitResource;
 #define RESOURCE_VERSION_MESH             RESOURCE_VERSION(4)
 #define RESOURCE_VERSION_MESH             RESOURCE_VERSION(4)
 #define RESOURCE_VERSION_PACKAGE          RESOURCE_VERSION(6)
 #define RESOURCE_VERSION_PACKAGE          RESOURCE_VERSION(6)
 #define RESOURCE_VERSION_PHYSICS_CONFIG   RESOURCE_VERSION(1)
 #define RESOURCE_VERSION_PHYSICS_CONFIG   RESOURCE_VERSION(1)
-#define RESOURCE_VERSION_SCRIPT           RESOURCE_VERSION(3)
+#define RESOURCE_VERSION_SCRIPT           RESOURCE_VERSION(4)
 #define RESOURCE_VERSION_SHADER           RESOURCE_VERSION(9)
 #define RESOURCE_VERSION_SHADER           RESOURCE_VERSION(9)
 #define RESOURCE_VERSION_SOUND            RESOURCE_VERSION(1)
 #define RESOURCE_VERSION_SOUND            RESOURCE_VERSION(1)
 #define RESOURCE_VERSION_SPRITE_ANIMATION RESOURCE_VERSION(1)
 #define RESOURCE_VERSION_SPRITE_ANIMATION RESOURCE_VERSION(1)