#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"
// It's common practice to include the C source directly for small-to-medium
// modules like this to simplify the build process.
#include "requests_backend.c"
#include "socket_backend.c"
#include "string_backend.c"
#include "sqlite3_backend.c"
#include "io_backend.c"
#include "crypto_backend.c"
#include "gtk_backend.c"
// --- 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);
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;
}
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);
}
if (strcmp(module, "sqlite") == 0) {
return bindSqliteForeignMethod(vm, module, className, isStatic, signature);
}
if (strcmp(module, "io") == 0) {
return bindIoForeignMethod(vm, module, className, isStatic, signature);
}
if (strcmp(className, "String") == 0) {
return bindStringForeignMethod(vm, module, className, isStatic, signature);
}
if (!strcmp(module,"gtk")){
return bindGtkForeignMethod(vm,module,className,isStatic,signature);
}
if (strcmp(module, "crypto") == 0) return bindCryptoForeignMethod(vm,module,className,isStatic,signature); // ← NEW
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);
}
if (strcmp(module, "sqlite") == 0) {
return bindSqliteForeignClass(vm, module, className);
}
if (strcmp(module, "io") == 0) {
WrenForeignClassMethods methods = {0, 0};
return methods;
}
if (strcmp(module, "gtk") == 0){
return bindForeignClass(vm, module, className);
}
WrenForeignClassMethods methods = {0, 0};
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);
// ** Initialize ALL managers **
socketManager_create(vm);
httpManager_create(vm);
dbManager_create(vm);
char* mainSource = readFile(argv[1]);
if (!mainSource) {
fprintf(stderr, "Could not open script: %s\n", argv[1]);
socketManager_destroy();
httpManager_destroy();
dbManager_destroy();
wrenFreeVM(vm);
curl_global_cleanup();
return 1;
}
wrenInterpret(vm, "main", mainSource);
free(mainSource);
if (g_mainFiberIsDone) {
socketManager_destroy();
httpManager_destroy();
dbManager_destroy();
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) {
// ** Process completions for ALL managers **
bool had_work = true;
socketManager_processCompletions();
httpManager_processCompletions();
dbManager_processCompletions();
// Resume the main Wren fiber
wrenEnsureSlots(vm, 1);
wrenSetSlotHandle(vm, 0, mainFiberHandle);
WrenInterpretResult result = wrenCall(vm, callHandle);
if (result == WREN_RESULT_RUNTIME_ERROR) {
g_mainFiberIsDone = true;
}
//socketManager_processCompletions();
//httpManager_processCompletions();
//dbManager_processCompletions();
if(!had_work) {
// Prevent 100% CPU usage
#ifdef _WIN32
Sleep(1);
#else
usleep(1); // 1ms
#endif
}
}
// Process any final completions before shutting down
wrenReleaseHandle(vm, mainFiberHandle);
wrenReleaseHandle(vm, callHandle);
// ** Destroy ALL managers **
socketManager_destroy();
httpManager_destroy();
dbManager_destroy();
wrenFreeVM(vm);
curl_global_cleanup();
printf("\nHost application finished.\n");
return 0;
}