diff --git a/source/cli/CMakeLists.txt b/source/cli/CMakeLists.txt index 872f10b77..4f9751728 100644 --- a/source/cli/CMakeLists.txt +++ b/source/cli/CMakeLists.txt @@ -6,3 +6,4 @@ endif() # CLI applications add_subdirectory(metacallcli) +add_subdirectory(plugins) diff --git a/source/cli/plugins/CMakeLists.txt b/source/cli/plugins/CMakeLists.txt new file mode 100644 index 000000000..37fbdd074 --- /dev/null +++ b/source/cli/plugins/CMakeLists.txt @@ -0,0 +1,13 @@ +#Check if extension loader is enabled +if(NOT OPTION_BUILD_LOADERS OR NOT OPTION_BUILD_LOADERS_EXT OR NOT OPTION_BUILD_EXTENSIONS) + return() +endif() + +# Extension sub-projects +add_subdirectory(core_plugin) + +#Install plugin directory +install(DIRECTORY ${PROJECT_OUTPUT_DIR}/plugins + DESTINATION ${INSTALL_LIB} + PATTERN "test[-_]*" EXCLUDE +) diff --git a/source/cli/plugins/core_plugin/CMakeLists.txt b/source/cli/plugins/core_plugin/CMakeLists.txt new file mode 100644 index 000000000..5c3749d46 --- /dev/null +++ b/source/cli/plugins/core_plugin/CMakeLists.txt @@ -0,0 +1,212 @@ +# Check if this loader is enabled +if(NOT OPTION_BUILD_LOADERS OR NOT OPTION_BUILD_LOADERS_EXT OR NOT OPTION_BUILD_EXTENSIONS OR NOT OPTION_BUILD_EXTENSIONS_LOAD) + return() +endif() + +# +# Plugin name and options +# + +# Target name +set(target core_plugin) + +# Exit here if required dependencies are not met +message(STATUS "Plugin ${target}") + +# Set API export file and macro +string(TOUPPER ${target} target_upper) +set(feature_file "include/${target}/${target}_features.h") +set(export_file "include/${target}/${target}_api.h") +set(export_macro "${target_upper}_API") + +# +# Compiler warnings +# + +include(Warnings) + +# +# Compiler security +# + +include(SecurityFlags) + +# +# Sources +# + +set(include_path "${CMAKE_CURRENT_SOURCE_DIR}/include/${target}") +set(source_path "${CMAKE_CURRENT_SOURCE_DIR}/source") + +set(headers + ${include_path}/core_plugin.h +) + +set(sources + ${source_path}/core_plugin.cpp +) + +# Group source files +set(header_group "Header Files (API)") +set(source_group "Source Files") +source_group_by_path(${include_path} "\\\\.h$|\\\\.hpp$" + ${header_group} ${headers}) +source_group_by_path(${source_path} "\\\\.cpp$|\\\\.c$|\\\\.h$|\\\\.hpp$" + ${source_group} ${sources}) + +# +# Create library +# + +# Build library +add_library(${target} MODULE + ${sources} + ${headers} +) + +# Create namespaced alias +add_library(${META_PROJECT_NAME}::${target} ALIAS ${target}) + +# Export library for downstream projects +export(TARGETS ${target} NAMESPACE ${META_PROJECT_NAME}:: FILE ${PROJECT_BINARY_DIR}/cmake/${target}/${target}-export.cmake) + +# Create feature detection header +# Compilers: https://cmake.org/cmake/help/v3.1/variable/CMAKE_LANG_COMPILER_ID.html#variable:CMAKE_%3CLANG%3E_COMPILER_ID +# Feature: https://cmake.org/cmake/help/v3.1/prop_gbl/CMAKE_CXX_KNOWN_FEATURES.html + +# Check for availability of module; use pre-generated version if not found +if (WriterCompilerDetectionHeaderFound) + write_compiler_detection_header( + FILE ${feature_file} + PREFIX ${target_upper} + COMPILERS AppleClang Clang GNU MSVC + FEATURES cxx_alignas cxx_alignof cxx_constexpr cxx_final cxx_noexcept cxx_nullptr cxx_sizeof_member cxx_thread_local + VERSION 3.2 + ) +else() + file( + COPY ${PROJECT_SOURCE_DIR}/codegeneration/${target}_features.h + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/include/${target} + USE_SOURCE_PERMISSIONS + ) +endif() + +# Create API export header +generate_export_header(${target} + EXPORT_FILE_NAME ${export_file} + EXPORT_MACRO_NAME ${export_macro} +) + +# +# Project options +# + +set_target_properties(${target} + PROPERTIES + ${DEFAULT_PROJECT_OPTIONS} + FOLDER "${IDE_FOLDER}" + BUNDLE $<$:$<$>> +) + +# +# Include directories +# + +target_include_directories(${target} + PRIVATE + ${PROJECT_BINARY_DIR}/source/include + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_BINARY_DIR}/include + + $ # MetaCall includes + + PUBLIC + ${DEFAULT_INCLUDE_DIRECTORIES} + + INTERFACE + $ + $ + $ +) + +# +# Libraries +# + +target_link_libraries(${target} + PRIVATE + ${META_PROJECT_NAME}::metacall # MetaCall library + + PUBLIC + ${DEFAULT_LIBRARIES} + + INTERFACE +) + +# +# Compile definitions +# + +target_compile_definitions(${target} + PRIVATE + + PUBLIC + $<$>:${target_upper}_STATIC_DEFINE> + ${DEFAULT_COMPILE_DEFINITIONS} + + INTERFACE +) + +# +# Compile options +# + +target_compile_options(${target} + PRIVATE + + PUBLIC + ${DEFAULT_COMPILE_OPTIONS} + + INTERFACE +) + +# +# Linker options +# + +target_link_libraries(${target} + PRIVATE + + PUBLIC + ${DEFAULT_LINKER_OPTIONS} + + INTERFACE +) + + +# +# Define dependencies +# + +add_dependencies(${target} + plugin_extension +) + +# +# Deployment +# + +#Copy metacall-*.json +add_custom_target(${target}-create-plugin-dir ALL + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_OUTPUT_DIR}/plugins/core_plugin + COMMAND ${CMAKE_COMMAND} -E copy ${source_path}/metacall-core_plugin.json ${PROJECT_OUTPUT_DIR}/plugins/core_plugin +) + +# Library +install(TARGETS ${target} + EXPORT "${target}-export" COMPONENT dev + RUNTIME DESTINATION ${INSTALL_BIN} COMPONENT runtime + LIBRARY DESTINATION ${INSTALL_SHARED} COMPONENT runtime + ARCHIVE DESTINATION ${INSTALL_LIB} COMPONENT dev +) diff --git a/source/cli/plugins/core_plugin/include/core_plugin/core_plugin.h b/source/cli/plugins/core_plugin/include/core_plugin/core_plugin.h new file mode 100644 index 000000000..2a6e9d5f2 --- /dev/null +++ b/source/cli/plugins/core_plugin/include/core_plugin/core_plugin.h @@ -0,0 +1,20 @@ +#ifndef CORE_PLUGIN_H +#define CORE_PLUGIN_H 1 + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +CORE_PLUGIN_API int core_plugin(void *loader, void *handle, void *context); + +DYNLINK_SYMBOL_EXPORT(core_plugin); + +#ifdef __cplusplus +} +#endif + +#endif /* CORE_PLUGIN_H */ diff --git a/source/cli/plugins/core_plugin/source/core_plugin.cpp b/source/cli/plugins/core_plugin/source/core_plugin.cpp new file mode 100644 index 000000000..0ee85412b --- /dev/null +++ b/source/cli/plugins/core_plugin/source/core_plugin.cpp @@ -0,0 +1,264 @@ +#include + +#include +#include + +#include + +#include +#include +#include +#include + +void *load(size_t argc, void *args[], void *data) +{ + (void)argc; + (void)data; + + char *tag = metacall_value_to_string(args[0]); + if (tag == NULL) + { + return metacall_value_create_int(1); + } + + size_t size = metacall_value_count(args[1]); + + char **scripts = (char **)malloc(sizeof(char *) * size); + void **script_val = metacall_value_to_array(args[1]); + if (scripts == NULL || script_val == NULL) + { + return metacall_value_create_int(1); + } + + for (size_t i = 0; i < size; ++i) + { + scripts[i] = metacall_value_to_string(script_val[i]); + } + + int ret = metacall_load_from_file(tag, const_cast(scripts), size, NULL); + + free(scripts); + + return metacall_value_create_int(ret); +} + +void *eval(size_t argc, void *args[], void *data) +{ + (void)argc; + (void)data; + char *tag = metacall_value_to_string(args[0]); + char *script = metacall_value_to_string(args[1]); + + int ret = metacall_load_from_memory(tag, script, strlen(script), NULL); + return metacall_value_create_int(ret); +} + +void *await(size_t argc, void *args[], void *data) +{ + (void)argc; + (void)data; + + /* Parse function call */ + std::string func_str = metacall_value_to_string(args[0]); + + std::string::size_type idx = func_str.find_first_of('('); + std::string func_name = func_str.substr(0, idx); + + /* Convert arguments into an array */ + std::string func_args = "["; + func_args += func_str.substr(idx + 1, func_str.size() - (idx + 2)); + func_args += "]"; + + /* Check if function is loaded */ + void *func = NULL; + if ((func = metacall_function(const_cast(func_name.c_str()))) == NULL) + { + return NULL; + } + + struct metacall_allocator_std_type std_ctx = { &std::malloc, &std::realloc, &std::free }; + + void *allocator = metacall_allocator_create(METACALL_ALLOCATOR_STD, (void *)&std_ctx); + + std::mutex await_mutex; /**< Mutex for blocking the process until await is resolved */ + std::condition_variable await_cond; /**< Condition to be fired once await method is resolved or rejected */ + + std::unique_lock lock(await_mutex); + + struct await_data_type + { + void *v; + std::mutex &mutex; + std::condition_variable &cond; + }; + + struct await_data_type fdata = { NULL, await_mutex, await_cond }; + + void *future = metacallfs_await( + func, func_args.c_str(), func_args.length() + 1, allocator, + [](void *result, void *ctx) -> void * { + struct await_data_type *await_data = static_cast(ctx); + std::unique_lock lock(await_data->mutex); + /* Value must be always copied, it gets deleted after the scope */ + await_data->v = metacall_value_copy(result); + await_data->cond.notify_one(); + return NULL; + }, + [](void *result, void *ctx) -> void * { + struct await_data_type *await_data = static_cast(ctx); + std::unique_lock lock(await_data->mutex); + /* Value must be always copied, it gets deleted after the scope */ + await_data->v = metacall_value_copy(result); + await_data->cond.notify_one(); + return NULL; + }, + static_cast(&fdata)); + + await_cond.wait(lock); + + return fdata.v; + /* Unused */ + metacall_value_destroy(future); + + metacall_allocator_destroy(allocator); +} + +void *call(size_t argc, void *args[], void *data) +{ + (void)argc; + (void)data; + + /* Parse function call */ + std::string func_str = metacall_value_to_string(args[0]); + + std::string::size_type idx = func_str.find_first_of('('); + std::string func_name = func_str.substr(0, idx); + + /* Convert arguments into an array */ + std::string func_args = "["; + func_args += func_str.substr(idx + 1, func_str.size() - (idx + 2)); + func_args += "]"; + + /* Check if function is loaded */ + void *func = NULL; + if ((func = metacall_function(const_cast(func_name.c_str()))) == NULL) + { + return NULL; //Todo: test this + } + + struct metacall_allocator_std_type std_ctx = { &std::malloc, &std::realloc, &std::free }; + + void *allocator = metacall_allocator_create(METACALL_ALLOCATOR_STD, (void *)&std_ctx); + + void *result = metacallfs(func, func_args.c_str(), func_args.length() + 1, allocator); + + metacall_allocator_destroy(allocator); + return result; +} + +void *clear(size_t argc, void *args[], void *data) +{ + (void)data; + + if (argc != 2) + { + return metacall_value_create_int(1); + } + + char *tag = metacall_value_to_string(args[0]); + char *script = metacall_value_to_string(args[1]); + + void *handle = metacall_handle(tag, script); + + if (handle == NULL) + { + log_write("metacall", LOG_LEVEL_DEBUG, "handle %s not found in loader (%s)", script, tag); + return metacall_value_create_int(1); + } + + if (metacall_clear(handle) != 0) + { + return metacall_value_create_int(1); + } + + return metacall_value_create_int(0); +} + +void *inspect(size_t argc, void *args[], void *data) +{ + (void)argc; + (void)args; + (void)data; + + size_t size = 0; + + struct metacall_allocator_std_type std_ctx = { &std::malloc, &std::realloc, &std::free }; + void *allocator = metacall_allocator_create(METACALL_ALLOCATOR_STD, (void *)&std_ctx); + + char *inspect_str = metacall_inspect(&size, allocator); + + metacall_allocator_destroy(allocator); + + return metacall_value_create_string(inspect_str, size); +} + +int core_plugin(void *loader, void *handle, void *context) +{ + (void)handle; + int ret = 0; + { + enum metacall_value_id *arg_types = NULL; + if (metacall_register_loaderv(loader, context, "inspect", inspect, METACALL_STRING, 0, arg_types) != 0) + { + log_write("metacall", LOG_LEVEL_DEBUG, "Failed to register function: inspect"); + ret = 1; + } + } + + { + enum metacall_value_id arg_types[] = { METACALL_STRING, METACALL_STRING }; + if (metacall_register_loaderv(loader, context, "clear", clear, METACALL_INT, sizeof(arg_types) / sizeof(arg_types[0]), arg_types) != 0) + { + log_write("metacall", LOG_LEVEL_DEBUG, "Failed to register function: clear"); + ret = 1; + } + } + + { + enum metacall_value_id arg_types[] = { METACALL_STRING }; + if (metacall_register_loaderv(loader, context, "call", call, METACALL_PTR, sizeof(arg_types) / sizeof(arg_types[0]), arg_types) != 0) + { + log_write("metacall", LOG_LEVEL_DEBUG, "Failed to register function: call"); + ret = 1; + } + } + + { + enum metacall_value_id arg_types[] = { METACALL_STRING }; + if (metacall_register_loaderv(loader, context, "await", await, METACALL_PTR, sizeof(arg_types) / sizeof(arg_types[0]), arg_types) != 0) + { + log_write("metacall", LOG_LEVEL_DEBUG, "Failed to register function: await"); + ret = 1; + } + } + + { + enum metacall_value_id arg_types[] = { METACALL_STRING, METACALL_STRING }; + if (metacall_register_loaderv(loader, context, "eval", eval, METACALL_INT, sizeof(arg_types) / sizeof(arg_types[0]), arg_types) != 0) + { + log_write("metacall", LOG_LEVEL_DEBUG, "Failed to register function: eval"); + ret = 1; + } + } + + { + enum metacall_value_id arg_types[] = { METACALL_STRING, METACALL_ARRAY }; + if (metacall_register_loaderv(loader, context, "load", load, METACALL_INT, sizeof(arg_types) / sizeof(arg_types[0]), arg_types) != 0) + { + log_write("metacall", LOG_LEVEL_DEBUG, "Failed to register function: load"); + ret = 1; + } + } + + return ret; +} diff --git a/source/cli/plugins/core_plugin/source/metacall-core_plugin.json b/source/cli/plugins/core_plugin/source/metacall-core_plugin.json new file mode 100644 index 000000000..6e0c7ab99 --- /dev/null +++ b/source/cli/plugins/core_plugin/source/metacall-core_plugin.json @@ -0,0 +1,6 @@ +{ + "language_id": "ext", + "scripts": [ + "core_plugin" + ] +} diff --git a/source/tests/CMakeLists.txt b/source/tests/CMakeLists.txt index 56e02286c..4188ead4d 100644 --- a/source/tests/CMakeLists.txt +++ b/source/tests/CMakeLists.txt @@ -230,3 +230,4 @@ add_subdirectory(metacall_library_path_without_env_vars_test) add_subdirectory(metacall_ext_test) add_subdirectory(metacall_plugin_extension_test) add_subdirectory(metacall_plugin_extension_local_test) +add_subdirectory(metacall_core_plugin_test) \ No newline at end of file diff --git a/source/tests/metacall_core_plugin_test/CMakeLists.txt b/source/tests/metacall_core_plugin_test/CMakeLists.txt new file mode 100644 index 000000000..9ebd642e1 --- /dev/null +++ b/source/tests/metacall_core_plugin_test/CMakeLists.txt @@ -0,0 +1,156 @@ +# Check if this loader is enabled +if(NOT OPTION_BUILD_LOADERS OR NOT OPTION_BUILD_LOADERS_EXT OR NOT OPTION_BUILD_LOADERS_NODE OR NOT OPTION_BUILD_LOADERS_PY OR NOT OPTION_BUILD_SCRIPTS_EXT OR NOT OPTION_BUILD_SCRIPTS_NODE OR NOT OPTION_BUILD_SCRIPTS_PY) + return() +endif() + +# +# Executable name and options +# + +# Target name +set(target metacall-core-plugin-test) +message(STATUS "Test ${target}") + +# +# Compiler warnings +# + +include(Warnings) + +# +# Compiler security +# + +include(SecurityFlags) + +# +# Sources +# + +set(include_path "${CMAKE_CURRENT_SOURCE_DIR}/include/${target}") +set(source_path "${CMAKE_CURRENT_SOURCE_DIR}/source") + +set(sources + ${source_path}/main.cpp + ${source_path}/metacall_core_plugin_test.cpp +) + +# Group source files +set(header_group "Header Files (API)") +set(source_group "Source Files") +source_group_by_path(${include_path} "\\\\.h$|\\\\.hpp$" + ${header_group} ${headers}) +source_group_by_path(${source_path} "\\\\.cpp$|\\\\.c$|\\\\.h$|\\\\.hpp$" + ${source_group} ${sources}) + +# +# Create executable +# + +# Build executable +add_executable(${target} + ${sources} +) + +# Create namespaced alias +add_executable(${META_PROJECT_NAME}::${target} ALIAS ${target}) + +# +# Project options +# + +set_target_properties(${target} + PROPERTIES + ${DEFAULT_PROJECT_OPTIONS} + FOLDER "${IDE_FOLDER}" +) + +# +# Include directories +# + +target_include_directories(${target} + PRIVATE + ${DEFAULT_INCLUDE_DIRECTORIES} + ${PROJECT_BINARY_DIR}/source/include +) + +# +# Libraries +# + +target_link_libraries(${target} + PRIVATE + ${DEFAULT_LIBRARIES} + + GTest + + ${META_PROJECT_NAME}::metacall +) + +# +# Compile definitions +# + +target_compile_definitions(${target} + PRIVATE + ${DEFAULT_COMPILE_DEFINITIONS} +) + +# +# Compile options +# + +target_compile_options(${target} + PRIVATE + ${DEFAULT_COMPILE_OPTIONS} +) + +# +#Define Macros +# + +add_compile_definitions(CORE_PLUGIN_PATH="${PROJECT_OUTPUT_DIR}/plugins/core_plugin/metacall-core_plugin.json") + +# +# Linker options +# + +target_link_libraries(${target} + PRIVATE + ${DEFAULT_LINKER_OPTIONS} +) + +# +# Define test +# + +add_test(NAME ${target} + COMMAND $ +) + +# +# Define dependencies +# + +add_dependencies(${target} + ext_loader + node_loader + py_loader + core_plugin +) + +# +# Define test properties +# + +set_property(TEST ${target} + PROPERTY LABELS ${target} +) + +include(TestEnvironmentVariables) + +test_environment_variables(${target} + "" + ${TESTS_ENVIRONMENT_VARIABLES} +) diff --git a/source/tests/metacall_core_plugin_test/source/main.cpp b/source/tests/metacall_core_plugin_test/source/main.cpp new file mode 100644 index 000000000..4676e4763 --- /dev/null +++ b/source/tests/metacall_core_plugin_test/source/main.cpp @@ -0,0 +1,28 @@ +/* + * Loader Library by Parra Studios + * A plugin for loading ruby code at run-time into a process. + * + * Copyright (C) 2016 - 2022 Vicente Eduardo Ferrer Garcia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include + +int main(int argc, char *argv[]) +{ + ::testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +} diff --git a/source/tests/metacall_core_plugin_test/source/metacall_core_plugin_test.cpp b/source/tests/metacall_core_plugin_test/source/metacall_core_plugin_test.cpp new file mode 100644 index 000000000..6bdf7c205 --- /dev/null +++ b/source/tests/metacall_core_plugin_test/source/metacall_core_plugin_test.cpp @@ -0,0 +1,169 @@ +/* + * Loader Library by Parra Studios + * A plugin for loading ruby code at run-time into a process. + * + * Copyright (C) 2016 - 2022 Vicente Eduardo Ferrer Garcia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include + +#include + +#include + +#include + +class metacall_core_plugin_test : public testing::Test +{ +protected: +}; + +TEST_F(metacall_core_plugin_test, DefaultConstructor) +{ + ASSERT_EQ((int)0, (int)metacall_initialize()); + + struct metacall_allocator_std_type std_ctx = { &std::malloc, &std::realloc, &std::free }; + + void *allocator = metacall_allocator_create(METACALL_ALLOCATOR_STD, (void *)&std_ctx); + + void *handle = NULL; + EXPECT_EQ((int)0, (int)metacall_load_from_configuration(CORE_PLUGIN_PATH, &handle, allocator)); + + void *ret = NULL; + { + void *args[2]; + + args[0] = metacall_value_create_string("py", 2); + + char test_script[] = "scripts/example.py"; + void *test_script_v = metacall_value_create_string(test_script, strlen(test_script)); + args[1] = metacall_value_create_array((const void **)&test_script_v, 1); + + ret = metacallhv_s(handle, "load", args, 2); + + EXPECT_NE((void *)NULL, (void *)ret); + EXPECT_EQ((long)metacall_value_to_int(ret), (long)0); + } + + { + void *args[2]; + args[0] = metacall_value_create_string("node", 4); + + char test_script[] = "scripts/nod.js"; + void *test_script_v = metacall_value_create_string(test_script, strlen(test_script)); + args[1] = metacall_value_create_array((const void **)&test_script_v, 1); + + ret = metacallhv_s(handle, "load", args, 2); + + EXPECT_NE((void *)NULL, (void *)ret); + EXPECT_EQ((long)metacall_value_to_int(ret), (long)0); + } + + { + void *args[2]; + + args[0] = metacall_value_create_string("py", 2); + + char func_call[] = "print('Testing core_plugin...')\n"; + args[1] = (void **)metacall_value_create_string(func_call, strlen(func_call)); + + ret = metacallhv_s(handle, "eval", args, 2); + + EXPECT_NE((void *)NULL, (void *)ret); + EXPECT_EQ((long)metacall_value_to_int(ret), (long)0); + } + + { + char func_call[] = "multiply(7, 3)"; + void *args[] = { metacall_value_create_string(func_call, strlen(func_call)) }; + + ret = metacallhv_s(handle, "call", args, 1); + + EXPECT_NE((void *)NULL, (void *)ret); + EXPECT_EQ((long)metacall_value_to_long(ret), (long)21); + } + + { + char func_call[] = "hello()"; + void *args[] = { metacall_value_create_string(func_call, strlen(func_call)) }; + + ret = metacallhv_s(handle, "call", args, 1); + + EXPECT_NE((void *)NULL, (void *)ret); + } + + { + char func_call[] = "hello_boy_await(2, 2)"; + void *args[] = { metacall_value_create_string(func_call, strlen(func_call)) }; + + ret = metacallhv_s(handle, "await", args, 1); + + EXPECT_NE((void *)NULL, (void *)ret); + EXPECT_EQ((long)metacall_value_to_double(ret), (double)4); + } + + { + char func_call[] = "return_await()"; + void *args[] = { metacall_value_create_string(func_call, strlen(func_call)) }; + + ret = metacallhv_s(handle, "await", args, 1); + + EXPECT_NE((void *)NULL, (void *)ret); + std::cout << metacall_value_to_string(ret) << '\n'; + } + + { + void *args[2]; + + args[0] = metacall_value_create_string("py", 2); + + char test_script[] = "scripts/example.py"; + args[1] = metacall_value_create_string(test_script, strlen(test_script)); + + ret = metacallhv_s(handle, "clear", args, 2); + + EXPECT_NE((void *)NULL, (void *)ret); + EXPECT_EQ((long)metacall_value_to_int(ret), (long)0); + } + + { + ret = metacallhv_s(handle, "inspect", metacall_null_args, 0); + + EXPECT_NE((void *)NULL, (void *)ret); + std::cout << metacall_value_to_string(ret) << '\n'; + } + + metacall_value_destroy(ret); + + /* Print inspect information */ + { + size_t size = 0; + + char *inspect_str = metacall_inspect(&size, allocator); + + EXPECT_NE((char *)NULL, (char *)inspect_str); + + EXPECT_GT((size_t)size, (size_t)0); + + std::cout << inspect_str << std::endl; + + metacall_allocator_free(allocator, inspect_str); + + metacall_allocator_destroy(allocator); + } + + EXPECT_EQ((int)0, (int)metacall_destroy()); +}