2025-07-29 14:35:38 +02:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include <curl/curl.h>
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
#include <windows.h>
|
|
|
|
#else
|
|
|
|
#include <unistd.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "wren.h"
|
2025-07-30 05:11:49 +02:00
|
|
|
|
|
|
|
// It's common practice to include the C source directly for small-to-medium
|
|
|
|
// modules like this to simplify the build process.
|
2025-07-29 14:35:38 +02:00
|
|
|
#include "requests_backend.c"
|
|
|
|
#include "socket_backend.c"
|
2025-07-30 05:11:49 +02:00
|
|
|
#include "string_backend.c"
|
|
|
|
#include "sqlite3_backend.c"
|
|
|
|
#include "io_backend.c"
|
2025-08-20 22:17:34 +02:00
|
|
|
#include "crypto_backend.c"
|
|
|
|
#include "gtk_backend.c"
|
2025-07-29 14:35:38 +02:00
|
|
|
// --- Global flag to control the main loop ---
|
|
|
|
static volatile bool g_mainFiberIsDone = false;
|
|
|
|
|
|
|
|
// --- Foreign function for Wren to signal the host to exit ---
|
|
|
|
void hostSignalDone(WrenVM* vm) {
|
|
|
|
(void)vm;
|
|
|
|
g_mainFiberIsDone = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// --- File/VM Setup ---
|
|
|
|
static char* readFile(const char* path) {
|
|
|
|
FILE* file = fopen(path, "rb");
|
|
|
|
if (file == NULL) return NULL;
|
|
|
|
fseek(file, 0L, SEEK_END);
|
|
|
|
size_t fileSize = ftell(file);
|
|
|
|
rewind(file);
|
|
|
|
char* buffer = (char*)malloc(fileSize + 1);
|
|
|
|
if (!buffer) { fclose(file); return NULL; }
|
|
|
|
size_t bytesRead = fread(buffer, sizeof(char), fileSize, file);
|
|
|
|
if (bytesRead < fileSize) {
|
|
|
|
free(buffer);
|
|
|
|
fclose(file);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
buffer[bytesRead] = '\0';
|
|
|
|
fclose(file);
|
|
|
|
return buffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void writeFn(WrenVM* vm, const char* text) { (void)vm; printf("%s", text); }
|
|
|
|
|
|
|
|
static void errorFn(WrenVM* vm, WrenErrorType type, const char* module, int line, const char* message) {
|
|
|
|
(void)vm;
|
|
|
|
switch (type) {
|
|
|
|
case WREN_ERROR_COMPILE:
|
|
|
|
fprintf(stderr, "[%s line %d] [Error] %s\n", module, line, message);
|
|
|
|
break;
|
|
|
|
case WREN_ERROR_RUNTIME:
|
|
|
|
fprintf(stderr, "[Runtime Error] %s\n", message);
|
|
|
|
g_mainFiberIsDone = true; // Stop on runtime errors
|
|
|
|
break;
|
|
|
|
case WREN_ERROR_STACK_TRACE:
|
|
|
|
fprintf(stderr, "[%s line %d] in %s\n", module, line, message);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void onModuleComplete(WrenVM* vm, const char* name, WrenLoadModuleResult result) {
|
|
|
|
(void)vm; (void)name;
|
|
|
|
if (result.source) free((void*)result.source);
|
|
|
|
}
|
|
|
|
|
|
|
|
static WrenLoadModuleResult loadModule(WrenVM* vm, const char* name) {
|
|
|
|
(void)vm;
|
|
|
|
WrenLoadModuleResult result = {0};
|
|
|
|
char path[256];
|
|
|
|
snprintf(path, sizeof(path), "%s.wren", name);
|
|
|
|
char* source = readFile(path);
|
2025-08-20 22:17:34 +02:00
|
|
|
|
|
|
|
if (strcmp(name, "main") == 0) {
|
|
|
|
// Load your main module with the String extensions
|
|
|
|
const char* source =
|
|
|
|
"import string\n"
|
|
|
|
"class String {\n"
|
|
|
|
" foreign endsWith(suffix)\n"
|
|
|
|
" foreign startsWith(prefix)\n"
|
|
|
|
" foreign replace(from, to)\n"
|
|
|
|
" foreign split(delimiter)\n"
|
|
|
|
" foreign toUpper()\n"
|
|
|
|
" foreign toLower()\n"
|
|
|
|
" foreign trim()\n"
|
|
|
|
" foreign trimStart()\n"
|
|
|
|
" foreign trimEnd()\n"
|
|
|
|
" foreign count(substring)\n"
|
|
|
|
" foreign indexOf(substring)\n"
|
|
|
|
" foreign lastIndexOf(substring)\n"
|
|
|
|
" foreign contains(substring)\n"
|
|
|
|
" foreign toInt()\n"
|
|
|
|
" foreign toNum()\n"
|
|
|
|
" foreign reverse()\n"
|
|
|
|
" foreign repeat(count)\n"
|
|
|
|
" foreign padStart(length, padString)\n"
|
|
|
|
" foreign padEnd(length, padString)\n"
|
|
|
|
" foreign charAt(index)\n"
|
|
|
|
" foreign charCodeAt(index)\n"
|
|
|
|
" foreign isNumeric()\n"
|
|
|
|
" foreign isAlpha()\n"
|
|
|
|
" foreign isAlphaNumeric()\n"
|
|
|
|
" foreign isEmpty()\n"
|
|
|
|
" foreign isWhitespace()\n"
|
|
|
|
" foreign capitalize()\n"
|
|
|
|
" foreign toCamelCase()\n"
|
|
|
|
" foreign toSnakeCase()\n"
|
|
|
|
" foreign toKebabCase()\n"
|
|
|
|
" foreign toPascalCase()\n"
|
|
|
|
" foreign swapCase()\n"
|
|
|
|
" foreign substring(start, end)\n"
|
|
|
|
" foreign slice(start)\n"
|
|
|
|
" foreign slice(start, end)\n"
|
|
|
|
" foreign toBytes()\n"
|
|
|
|
" static foreign length()\n"
|
|
|
|
" \n"
|
|
|
|
" static foreign join(list, separator)\n"
|
|
|
|
" static foreign fromCharCode(code)\n"
|
|
|
|
"}\n";
|
|
|
|
|
|
|
|
result.source = source;
|
|
|
|
}
|
|
|
|
|
2025-07-29 14:35:38 +02:00
|
|
|
if (source != NULL) {
|
|
|
|
result.source = source;
|
|
|
|
result.onComplete = onModuleComplete;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// --- Combined Foreign Function Binders ---
|
|
|
|
WrenForeignMethodFn combinedBindForeignMethod(WrenVM* vm, const char* module, const char* className, bool isStatic, const char* signature) {
|
|
|
|
if (strcmp(module, "socket") == 0) {
|
|
|
|
return bindSocketForeignMethod(vm, module, className, isStatic, signature);
|
|
|
|
}
|
|
|
|
if (strcmp(module, "requests") == 0) {
|
|
|
|
return bindForeignMethod(vm, module, className, isStatic, signature);
|
|
|
|
}
|
2025-07-30 05:11:49 +02:00
|
|
|
if (strcmp(module, "sqlite") == 0) {
|
|
|
|
return bindSqliteForeignMethod(vm, module, className, isStatic, signature);
|
|
|
|
}
|
|
|
|
if (strcmp(module, "io") == 0) {
|
|
|
|
return bindIoForeignMethod(vm, module, className, isStatic, signature);
|
|
|
|
}
|
2025-07-29 15:14:47 +02:00
|
|
|
if (strcmp(className, "String") == 0) {
|
|
|
|
return bindStringForeignMethod(vm, module, className, isStatic, signature);
|
|
|
|
}
|
2025-08-20 22:17:34 +02:00
|
|
|
if (!strcmp(module,"gtk")){
|
|
|
|
return bindGtkForeignMethod(vm,module,className,isStatic,signature);
|
|
|
|
}
|
|
|
|
if (strcmp(module, "crypto") == 0) return bindCryptoForeignMethod(vm,module,className,isStatic,signature); // ← NEW
|
|
|
|
|
2025-07-29 14:35:38 +02:00
|
|
|
if (strcmp(module, "main") == 0 && strcmp(className, "Host") == 0 && isStatic) {
|
|
|
|
if (strcmp(signature, "signalDone()") == 0) return hostSignalDone;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
WrenForeignClassMethods combinedBindForeignClass(WrenVM* vm, const char* module, const char* className) {
|
|
|
|
if (strcmp(module, "socket") == 0) {
|
|
|
|
return bindSocketForeignClass(vm, module, className);
|
|
|
|
}
|
|
|
|
if (strcmp(module, "requests") == 0) {
|
|
|
|
return bindForeignClass(vm, module, className);
|
|
|
|
}
|
2025-07-30 05:11:49 +02:00
|
|
|
if (strcmp(module, "sqlite") == 0) {
|
|
|
|
return bindSqliteForeignClass(vm, module, className);
|
|
|
|
}
|
|
|
|
if (strcmp(module, "io") == 0) {
|
|
|
|
WrenForeignClassMethods methods = {0, 0};
|
|
|
|
return methods;
|
|
|
|
}
|
2025-08-20 22:17:34 +02:00
|
|
|
if (strcmp(module, "gtk") == 0){
|
|
|
|
return bindForeignClass(vm, module, className);
|
|
|
|
}
|
2025-07-29 14:35:38 +02:00
|
|
|
WrenForeignClassMethods methods = {0, 0};
|
2025-08-20 22:17:34 +02:00
|
|
|
|
2025-07-29 14:35:38 +02:00
|
|
|
return methods;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// --- Main Application Entry Point ---
|
|
|
|
int main(int argc, char* argv[]) {
|
|
|
|
if (argc < 2) {
|
|
|
|
fprintf(stderr, "Usage: %s <script.wren>\n", argv[0]);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
curl_global_init(CURL_GLOBAL_ALL);
|
|
|
|
|
|
|
|
WrenConfiguration config;
|
|
|
|
wrenInitConfiguration(&config);
|
|
|
|
config.writeFn = writeFn;
|
|
|
|
config.errorFn = errorFn;
|
|
|
|
config.bindForeignMethodFn = combinedBindForeignMethod;
|
|
|
|
config.bindForeignClassFn = combinedBindForeignClass;
|
|
|
|
config.loadModuleFn = loadModule;
|
|
|
|
|
|
|
|
WrenVM* vm = wrenNewVM(&config);
|
|
|
|
|
2025-07-30 05:11:49 +02:00
|
|
|
// ** Initialize ALL managers **
|
2025-07-29 14:35:38 +02:00
|
|
|
socketManager_create(vm);
|
|
|
|
httpManager_create(vm);
|
2025-07-30 05:11:49 +02:00
|
|
|
dbManager_create(vm);
|
2025-07-29 14:35:38 +02:00
|
|
|
|
|
|
|
char* mainSource = readFile(argv[1]);
|
|
|
|
if (!mainSource) {
|
|
|
|
fprintf(stderr, "Could not open script: %s\n", argv[1]);
|
|
|
|
socketManager_destroy();
|
|
|
|
httpManager_destroy();
|
2025-07-30 05:11:49 +02:00
|
|
|
dbManager_destroy();
|
2025-07-29 14:35:38 +02:00
|
|
|
wrenFreeVM(vm);
|
|
|
|
curl_global_cleanup();
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
wrenInterpret(vm, "main", mainSource);
|
|
|
|
free(mainSource);
|
|
|
|
|
|
|
|
if (g_mainFiberIsDone) {
|
|
|
|
socketManager_destroy();
|
|
|
|
httpManager_destroy();
|
2025-07-30 05:11:49 +02:00
|
|
|
dbManager_destroy();
|
2025-07-29 14:35:38 +02:00
|
|
|
wrenFreeVM(vm);
|
|
|
|
curl_global_cleanup();
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
wrenEnsureSlots(vm, 1);
|
|
|
|
wrenGetVariable(vm, "main", "mainFiber", 0);
|
|
|
|
WrenHandle* mainFiberHandle = wrenGetSlotHandle(vm, 0);
|
|
|
|
WrenHandle* callHandle = wrenMakeCallHandle(vm, "call()");
|
|
|
|
|
|
|
|
// === Main Event Loop ===
|
|
|
|
while (!g_mainFiberIsDone) {
|
2025-07-30 05:11:49 +02:00
|
|
|
// ** Process completions for ALL managers **
|
2025-08-20 22:17:34 +02:00
|
|
|
bool had_work = true;
|
|
|
|
|
2025-07-29 14:35:38 +02:00
|
|
|
socketManager_processCompletions();
|
|
|
|
httpManager_processCompletions();
|
2025-07-30 05:11:49 +02:00
|
|
|
dbManager_processCompletions();
|
2025-07-29 14:35:38 +02:00
|
|
|
|
|
|
|
// Resume the main Wren fiber
|
|
|
|
wrenEnsureSlots(vm, 1);
|
|
|
|
wrenSetSlotHandle(vm, 0, mainFiberHandle);
|
|
|
|
WrenInterpretResult result = wrenCall(vm, callHandle);
|
2025-07-30 05:11:49 +02:00
|
|
|
|
2025-07-29 14:35:38 +02:00
|
|
|
if (result == WREN_RESULT_RUNTIME_ERROR) {
|
|
|
|
g_mainFiberIsDone = true;
|
|
|
|
}
|
2025-08-20 22:17:34 +02:00
|
|
|
//socketManager_processCompletions();
|
|
|
|
//httpManager_processCompletions();
|
|
|
|
//dbManager_processCompletions();
|
2025-07-29 14:35:38 +02:00
|
|
|
|
2025-08-20 22:17:34 +02:00
|
|
|
if(!had_work) {
|
|
|
|
|
|
|
|
|
2025-07-29 14:35:38 +02:00
|
|
|
// Prevent 100% CPU usage
|
|
|
|
#ifdef _WIN32
|
|
|
|
Sleep(1);
|
|
|
|
#else
|
2025-08-20 22:17:34 +02:00
|
|
|
usleep(1); // 1ms
|
2025-07-29 14:35:38 +02:00
|
|
|
#endif
|
2025-08-20 22:17:34 +02:00
|
|
|
}
|
2025-07-29 14:35:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Process any final completions before shutting down
|
|
|
|
wrenReleaseHandle(vm, mainFiberHandle);
|
|
|
|
wrenReleaseHandle(vm, callHandle);
|
|
|
|
|
2025-07-30 05:11:49 +02:00
|
|
|
// ** Destroy ALL managers **
|
2025-07-29 14:35:38 +02:00
|
|
|
socketManager_destroy();
|
|
|
|
httpManager_destroy();
|
2025-07-30 05:11:49 +02:00
|
|
|
dbManager_destroy();
|
2025-07-29 14:35:38 +02:00
|
|
|
|
|
|
|
wrenFreeVM(vm);
|
|
|
|
curl_global_cleanup();
|
|
|
|
|
|
|
|
printf("\nHost application finished.\n");
|
|
|
|
return 0;
|
|
|
|
}
|
2025-07-30 05:11:49 +02:00
|
|
|
|