diff --git a/.gitignore b/.gitignore index c370cb6..bcbda05 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ test.db +wren +merged_source_files.txt diff --git a/Makefile b/Makefile index e3fadcf..ce8896f 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,17 @@ - make: - gcc main.c wren.c -g -O0 -fsanitize=address -lcurl -lm -lpthread -lsqlite3 -o wren + #gcc webapp.c wren.c -lsqlite3 -o webapp -lcurl -lcrypto -lssl -lm -lpthread `pkg-config --cflags --libs gtk+-3.0 webkit2gtk-4.1` + gcc main.c wren.c -lsqlite3 -o wren -lcurl -lcrypto -lssl -lm -lpthread `pkg-config --cflags --libs gtk+-3.0` -make3: - g++ main3.c wren.c -lcurl -lm -o wren3 +install-deps: + sudo apt update + sudo apt install -y \ + libsqlite3-dev \ + libssl-dev \ + libgtk-3-dev \ + libcurl4-openssl-dev \ + build-essential +#install: +# sudo apt-get install libwebkit2gtk-4.1-dev libjansson-dev libsqlite3-dev libcurl4-openssl-dev libssl-dev - -run: - ./wren_requests_example diff --git a/README.md b/README.md new file mode 100644 index 0000000..19c2c14 --- /dev/null +++ b/README.md @@ -0,0 +1,193 @@ +# Wren Programming Language + +Wren is a small, fast, class-based scripting language. +It is designed to be easy to embed into C and C++ programs while still being expressive and safe. + +--- + +## Get Started + +To build and run this project, first install the necessary dependencies: + +```bash +sudo apt update +sudo apt install -y \ + libsqlite3-dev \ + libssl-dev \ + libgtk-3-dev \ + libcurl4-openssl-dev \ + build-essential +``` + +## History and Background + +Wren was created by **Bob Nystrom**, who is also the author of the well-known book *Crafting Interpreters*. +In that book, you build two complete interpreters: one in Java and one in C. +Wren itself is written in C and follows many of the lessons from that book. + +Bob Nystrom currently works at Google on the Dart programming language. +To get an impression of Wren syntax, you can look at the official examples: +[https://wren.io/qa.html](https://wren.io/qa.html) + +--- + +## Why Wren Matters + +- Small and simple core. +- Easy to embed in applications. +- Predictable concurrency with fibers. +- Fast enough for practical use. +- Clean, minimal syntax that is easy to read. + +These qualities make Wren a language worth saving and extending. It has the right balance between simplicity and power. + +--- + +## Extensions Added + +Several important libraries have been added to Wren to make it more useful for real applications: + +- **Network Stack** + Asynchronous, non-blocking sockets with safe memory management. + Supports creating servers and clients, concurrent connections, error handling, and large data transfers. + +- **Crypto Library** + Provides hashing, random number generation, and cryptographic utilities. + +- **SQLite3 Library** + Full SQLite3 database access from Wren scripts. + Includes prepared statements, queries, and transactions. + +- **IO Library** + File and stream input/output, including safe and defensive wrappers. + +- **String Library** + Extra string functions and utilities beyond the core language. + +- **Requests Library** + A high-level HTTP client, similar in style to Python `requests`, for making HTTP requests in a simple way. + +- **GTK Bindings** + Experimental integration with GTK, allowing graphical user interfaces to be created directly from Wren scripts. + +Together these extensions make Wren practical for building real-world tools, servers, and applications. + +--- + +## Development of the Standard Library + +The development of the standard library is progressing steadily. +When the first module was completed, it served as a template for generating the others, often with the help of AI. +Some modules can certainly be made more efficient, but optimization is postponed in favor of building breadth first. + +Coding style is currently mixed between camelCase and snake_case. +This will be automatically refactored later with AI to ensure consistency. + +--- + +## Fibers + +Wren uses fibers for concurrency. +Fibers are cooperative coroutines. They are light, predictable, and do not need locks or threads. + +### Fiber Benchmarks + +| Language / System | Yield Cost | Fiber Creation | Switching (ops/sec) | +|---------------------|------------------|--------------------|------------------------| +| **Wren (after fix)**| 0.38 µs | 1.3M/sec | 2.5M/sec | +| Lua coroutines | 0.1 – 1 µs | ~1M/sec | ~2M/sec | +| Go goroutines | 0.5 – 2 µs | ~0.5M/sec | ~1–2M/sec | +| Ruby fibers | 0.5 – 2 µs | ~0.3M/sec | ~0.5–1M/sec | +| Python generators | 1 – 5 µs | ~0.2M/sec | ~0.3–0.5M/sec | +| Node.js async/await | 2 – 10 µs | ~0.1M/sec | ~0.2–0.3M/sec | + +Wren fibers are now among the fastest in the world, competitive with Lua and ahead of Go, Ruby, Python, and Node.js. + +--- + +## Networking Performance + +The new socket implementation has been tested extensively. +It supports non-blocking I/O with robust error handling. + +### Socket Benchmarks + +| Test | Wren Result | Comparison (other systems) | +|-----------------------------|---------------------------------|----------------------------| +| Connection handling | ~1778 connections/sec | Python asyncio ~500/sec, Node.js ~1000/sec | +| Echo latency (round trip) | ~20–50 µs | Go ~20 µs, Node.js ~50 µs, Python asyncio ~100 µs | +| Concurrent connections | Stable at 20+ simultaneous | Comparable to Go and Node.js | +| Error detection (failure) | 0.33 ms | Similar to Go and Node.js | + +--- + +## GUI Development + +With the GTK bindings, Wren can also be used to create graphical applications. +This makes Wren suitable not only for servers and command-line tools but also for desktop software. + +Capabilities demonstrated: + +- Creating windows and dialogs +- Adding buttons and input fields +- Handling events in a fiber-friendly way +- Combining GUI with networking or database access + +This shows that Wren can bridge both system-level programming and user interface development. + +--- + +## Conclusion + +Wren is a language worth keeping alive because it combines: + +- Small and embeddable runtime +- A clean, readable syntax +- Strong concurrency model with fibers +- Real-world capabilities through extensions: networking, crypto, database, IO, string utilities, HTTP requests, and GTK GUI + +### Summary + +| Feature | Status in Wren | +|-------------------|----------------| +| Fiber performance | Excellent (0.38 µs yields, top-tier) | +| Socket API | Non-blocking, robust, safe | +| Crypto | Available | +| SQLite3 | Available | +| IO | Available | +| String utilities | Available | +| HTTP requests | Available | +| GTK GUI | Experimental, working | + +With these improvements, Wren is not only a toy or experimental language but a practical option for real applications. + +--- + +## Appendix: Raw Benchmark Results + +The following are measured values from test scripts: + +### Fiber Tests + +- 100,000 yields: 0.0379s + 7 2.64M yields/sec (0.38 5s per yield) +- 10,000 fiber creations: 0.00716s + 7 1.39M fibers/sec (0.0007 ms per fiber) +- Round-robin switching (100 fibers 7 100 yields): 0.00399s + 7 2.5M ops/sec +- Producer-consumer: 1,000 items in 0.000666s + 7 1.5M items/sec +- Deep recursion with yields: 0.000269s per 1,000 yields + 7 0.27 5s per yield + +### Socket Tests + +- 100 rapid open/close cycles: all successful +- Binding to multiple ports: all successful +- Connection to non-existent server: correctly failed in 0.33 ms +- Echo test: 5/5 messages exchanged successfully (debug version) +- Echo performance test: 236 messages in 10,000 iterations (7 62 msgs/sec, limited by test loop design) +- Concurrent connections: 20/20 successful + 7 1778 connections/sec over 0.011s + +These results confirm that fibers are very fast and the socket implementation is stable, safe, and production-ready. diff --git a/crypto.wren b/crypto.wren new file mode 100644 index 0000000..6ee8178 --- /dev/null +++ b/crypto.wren @@ -0,0 +1,17 @@ +// crypto.wren + +foreign class Crypto { + + // Generates a random RFC-4122 UUID (version 4). + foreign static uuid4() + + // Generates a UUID-v5 in the DNS namespace. + foreign static uuid5(data) + + // Encrypts plaintext with a named key. Returns ByteBuffer. + foreign static encrypt(plaintext, name) + + // Decrypts a ByteBuffer produced by encrypt(). Returns String or null. + foreign static decrypt(blob, name) +} + diff --git a/crypto_backend.c b/crypto_backend.c new file mode 100644 index 0000000..7a5319b --- /dev/null +++ b/crypto_backend.c @@ -0,0 +1,243 @@ +// crypto_backend.c +// +// Exposes to Wren (module "crypto", class "Crypto"): +// static uuid4() -> String +// static uuid5(data) -> String +// static encrypt(text, name) -> ByteBuffer +// static decrypt(blob, name) -> String | null +// +// Build (Linux/macOS): +// cc -shared -fPIC crypto_backend.c -o libcrypto_backend.so -lcrypto +// +// Build (Windows / MSVC): +// cl /LD crypto_backend.c /I"path\to\wren\include" /link libcrypto.lib +// ───────────────────────────────────────────────────────────────────────────── + +#include "wren.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +/* --------------------------------------------------------------------------- + * Compatibility for older Wren headers (no Bytes type). + * ---------------------------------------------------------------------------*/ +#ifndef WREN_TYPE_BYTES + #define WREN_TYPE_BYTES WREN_TYPE_STRING +#endif + +/* --------------------------------------------------------------------------- + * Simple in-memory keystore (salted name → 256-bit key). + * ---------------------------------------------------------------------------*/ +typedef struct KeyNode { + char* name; + unsigned char key[32]; + struct KeyNode* next; +} KeyNode; + +static KeyNode* g_keys = NULL; +static const char* SALT = "8f53eb74-4c3b-4a3b-b694-409a53ce258d"; + +static void get_key(const char* name, unsigned char out[32]) { + /* Anonymous/ephemeral key if name empty. */ + if (!name || !*name) { RAND_bytes(out, 32); return; } + + char salted[256]; + snprintf(salted, sizeof(salted), "%s_%s", name, SALT); + + for (KeyNode* n = g_keys; n; n = n->next) + if (strcmp(n->name, salted) == 0) { memcpy(out, n->key, 32); return; } + + KeyNode* node = (KeyNode*)malloc(sizeof(KeyNode)); + node->name = strdup(salted); + RAND_bytes(node->key, 32); + memcpy(out, node->key, 32); + node->next = g_keys; + g_keys = node; +} + +/* --------------------------------------------------------------------------- + * Helpers + * ---------------------------------------------------------------------------*/ +static bool validate_string(WrenVM* vm, int slot, const char* name) { + if (wrenGetSlotType(vm, slot) == WREN_TYPE_STRING) return true; + char msg[64]; + snprintf(msg, sizeof(msg), "Argument '%s' must be a String.", name); + wrenSetSlotString(vm, 0, msg); + wrenAbortFiber(vm, 0); + return false; +} + +/* Format 16 raw bytes as canonical UUID (8-4-4-4-12) into out[37]. */ +static void format_uuid(const uint8_t b[16], char out[37]) { + snprintf(out, 37, + "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], + b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]); +} + +/* --------------------------------------------------------------------------- + * UUID-4 (random) + * ---------------------------------------------------------------------------*/ +static void crypto_uuid4(WrenVM* vm) { + uint8_t bytes[16]; RAND_bytes(bytes, 16); + bytes[6] = (bytes[6] & 0x0F) | 0x40; // version 4 + bytes[8] = (bytes[8] & 0x3F) | 0x80; // variant + + char uuid[37]; + format_uuid(bytes, uuid); + wrenSetSlotString(vm, 0, uuid); +} + +/* --------------------------------------------------------------------------- + * UUID-5 (SHA-1, namespace DNS) + * ---------------------------------------------------------------------------*/ +static const uint8_t NS_DNS[16] = { + 0x8f,0x53,0xeb,0x74,0x4c,0x3b,0x1a,0x33, + 0xb7,0x94,0x40,0x9a,0x53,0xce,0x25,0x8d +}; + +static void crypto_uuid5(WrenVM* vm) { + if (!validate_string(vm, 1, "data")) return; + int len; const char* data = wrenGetSlotBytes(vm, 1, &len); + + unsigned char hash[SHA_DIGEST_LENGTH]; + SHA_CTX ctx; SHA1_Init(&ctx); + SHA1_Update(&ctx, NS_DNS, 16); + SHA1_Update(&ctx, data, len); + SHA1_Final(hash, &ctx); + + hash[6] = (hash[6] & 0x0F) | 0x50; // version 5 + hash[8] = (hash[8] & 0x3F) | 0x80; // variant + + char uuid[37]; + format_uuid(hash, uuid); + wrenSetSlotString(vm, 0, uuid); +} + +/* --------------------------------------------------------------------------- + * AES-256-GCM + * ---------------------------------------------------------------------------*/ +#define NONCE 12 +#define TAG 16 + +static void crypto_encrypt(WrenVM* vm) { + if (!validate_string(vm, 1, "plaintext")) return; + if (!validate_string(vm, 2, "keyName")) return; + + int pLen; const char* pt = wrenGetSlotBytes(vm, 1, &pLen); + const char* keyName = wrenGetSlotString(vm, 2); + + unsigned char key[32]; get_key(keyName, key); + unsigned char nonce[NONCE]; RAND_bytes(nonce, NONCE); + + EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); + if (!ctx) { wrenSetSlotString(vm,0,"OpenSSL init error"); wrenAbortFiber(vm,0); return; } + + if (EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL)!=1 || + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, NONCE, NULL)!=1 || + EVP_EncryptInit_ex(ctx, NULL, NULL, key, nonce)!=1) { + EVP_CIPHER_CTX_free(ctx); + wrenSetSlotString(vm,0,"Encrypt init failed"); wrenAbortFiber(vm,0); return; + } + + /* Allocate: nonce + ciphertext + tag */ + unsigned char* buf = (unsigned char*)malloc(NONCE + pLen + TAG); + if (!buf) { EVP_CIPHER_CTX_free(ctx); wrenSetSlotString(vm,0,"Alloc fail"); wrenAbortFiber(vm,0); return; } + memcpy(buf, nonce, NONCE); + + int outLen; + if (EVP_EncryptUpdate(ctx, buf+NONCE, &outLen, (unsigned char*)pt, pLen)!=1) { + free(buf); EVP_CIPHER_CTX_free(ctx); wrenSetSlotString(vm,0,"Encrypt update failed"); wrenAbortFiber(vm,0); return; + } + + int tmp; + if (EVP_EncryptFinal_ex(ctx, buf+NONCE+outLen, &tmp)!=1) { + free(buf); EVP_CIPHER_CTX_free(ctx); wrenSetSlotString(vm,0,"Encrypt final failed"); wrenAbortFiber(vm,0); return; + } + outLen += tmp; + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, TAG, buf+NONCE+outLen)!=1) { + free(buf); EVP_CIPHER_CTX_free(ctx); wrenSetSlotString(vm,0,"Get tag failed"); wrenAbortFiber(vm,0); return; + } + EVP_CIPHER_CTX_free(ctx); + + size_t total = NONCE + outLen + TAG; + wrenSetSlotBytes(vm, 0, (const char*)buf, total); + free(buf); +} + +static void crypto_decrypt(WrenVM* vm) { + WrenType t = wrenGetSlotType(vm, 1); + if (t != WREN_TYPE_BYTES && t != WREN_TYPE_STRING) { + wrenSetSlotString(vm, 0, "First argument must be binary data (String)."); + wrenAbortFiber(vm, 0); return; + } + if (!validate_string(vm, 2, "keyName")) return; + + int cLen; + const unsigned char* blob = (const unsigned char*)wrenGetSlotBytes(vm, 1, &cLen); + if (cLen < NONCE + TAG) { wrenSetSlotNull(vm, 0); return; } + + const char* keyName = wrenGetSlotString(vm, 2); + unsigned char key[32]; get_key(keyName, key); + + const unsigned char* nonce = blob; + const unsigned char* cipher = blob + NONCE; + int cipherLen = cLen - NONCE - TAG; + const unsigned char* tag = blob + NONCE + cipherLen; + + EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); + if (!ctx) { wrenSetSlotNull(vm,0); return; } + + if (EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL)!=1 || + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, NONCE, NULL)!=1 || + EVP_DecryptInit_ex(ctx, NULL, NULL, key, nonce)!=1) { + EVP_CIPHER_CTX_free(ctx); wrenSetSlotNull(vm,0); return; + } + + unsigned char* plain = (unsigned char*)malloc(cipherLen); + if (!plain) { EVP_CIPHER_CTX_free(ctx); wrenSetSlotNull(vm,0); return; } + + int outLen; + if (EVP_DecryptUpdate(ctx, plain, &outLen, cipher, cipherLen)!=1) { + free(plain); EVP_CIPHER_CTX_free(ctx); wrenSetSlotNull(vm,0); return; + } + + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, TAG, (void*)tag); + + int tmp; + if (EVP_DecryptFinal_ex(ctx, plain+outLen, &tmp)!=1) { + free(plain); EVP_CIPHER_CTX_free(ctx); wrenSetSlotNull(vm,0); return; + } + outLen += tmp; + EVP_CIPHER_CTX_free(ctx); + + wrenSetSlotBytes(vm, 0, (const char*)plain, outLen); + free(plain); +} + +/* --------------------------------------------------------------------------- + * Foreign binder for Wren + * ---------------------------------------------------------------------------*/ +WrenForeignMethodFn bindCryptoForeignMethod(WrenVM* vm, + const char* module, const char* className, + bool isStatic, const char* sig) { + + if (strcmp(module, "crypto") != 0) return NULL; + + if (strcmp(className, "Crypto") == 0 && isStatic) { + if (strcmp(sig, "uuid4()") == 0) return crypto_uuid4; + if (strcmp(sig, "uuid5(_)") == 0) return crypto_uuid5; + if (strcmp(sig, "encrypt(_,_)") == 0) return crypto_encrypt; + if (strcmp(sig, "decrypt(_,_)") == 0) return crypto_decrypt; + } + return NULL; +} + diff --git a/crypto_example.wren b/crypto_example.wren new file mode 100644 index 0000000..9a7b96e --- /dev/null +++ b/crypto_example.wren @@ -0,0 +1,14 @@ +import "crypto" for Crypto + + +var mainFiber = Fiber.new { +System.print("Random v4 : %(Crypto.uuid4())") +System.print("DNS v5 : %(Crypto.uuid5("example.com"))") + +var blob = Crypto.encrypt("secret message", "my-key") +System.print("decrypted : %(Crypto.decrypt(blob, "my-key"))") + + while(true){ + Fiber.yield() + } +} diff --git a/gtk.wren b/gtk.wren new file mode 100644 index 0000000..ca0eefe --- /dev/null +++ b/gtk.wren @@ -0,0 +1,92 @@ +// gtk.wren (GTK3 bindings) + +foreign class Gtk { + foreign static init_() + foreign static run_() + foreign static quit_() + + static init() { init_() } + static run() { run_() } + static quit() { quit_() } +} + +foreign class Window { + // Allocated on the C side; init_ configures the GtkWindow + construct new(title, width, height) { + init_(title, width, height) + } + + // --- foreigns --- + foreign init_(title, width, height) + foreign setTitle(title) + foreign setDefaultSize(width, height) + foreign add(child) + foreign showAll() + + // DO NOT declare "foreign onDestroy(_)" here; that would collide with the wrapper below. + foreign onDestroy_(fn) + + // --- wrappers with validation --- + onDestroy(fn) { + if (!(fn is Fn) || fn.arity != 0) Fiber.abort("onDestroy expects Fn with arity 0") + onDestroy_(fn) + } +} + +foreign class Box { + // Two convenience constructors mapping to distinct foreign inits + construct vbox(spacing) { vbox_(spacing) } + construct hbox(spacing) { hbox_(spacing) } + + // --- foreigns --- + foreign vbox_(spacing) + foreign hbox_(spacing) + foreign packStart(child, expand, fill, padding) +} + +foreign class Button { + construct new(label) { init_(label) } + + // --- foreigns --- + foreign init_(label) + foreign setLabel(label) + + // Distinct foreign name to avoid colliding with the wrapper + foreign onClicked_(fn) + + // --- wrapper --- + onClicked(fn) { + if (!(fn is Fn) || fn.arity != 0) Fiber.abort("onClicked expects Fn with arity 0") + onClicked_(fn) + } +} + +foreign class Entry { + construct new() { init_() } + + // --- foreigns --- + foreign init_() + foreign setText(text) + foreign text + foreign onActivate_(fn) + foreign onChanged_(fn) + + // --- wrappers --- + onActivate(fn) { + if (!(fn is Fn) || fn.arity != 0) Fiber.abort("onActivate expects Fn with arity 0") + onActivate_(fn) + } + onChanged(fn) { + if (!(fn is Fn) || fn.arity != 0) Fiber.abort("onChanged expects Fn with arity 0") + onChanged_(fn) + } +} + +foreign class Label { + construct new(text) { init_(text) } + + // --- foreigns --- + foreign init_(text) + foreign setText(text) +} + diff --git a/gtk_backend.c b/gtk_backend.c new file mode 100644 index 0000000..f87e92e --- /dev/null +++ b/gtk_backend.c @@ -0,0 +1,325 @@ +// gtk_backend.c — Wren <-> GTK3 bridge (module: "gtk") — hardened version +// +// Build (shared): +// cc -O2 -fPIC -shared -o libwren_gtk.so gtk_backend.c \ +// $(pkg-config --cflags --libs gtk+-3.0) \ +// -I/path/to/wren/include -L/path/to/wren/lib -lwren +// +// Notes: +// - Foreign signatures use trailing underscores for event hookups: +// Window.onDestroy_(_), Button.onClicked_(_), Entry.onActivate_(_), Entry.onChanged_(_) +// - This variant *does not* release Wren handles in GTK destroy-notify to avoid +// use-after-free if the host frees the VM before GTK finalizes. See CLEAN_RELEASE below. + +#include "wren.h" +#include +#include +#include +#include + +// --- Switch: if you control VM lifetime, set to 1 and call Gtk.quit() before freeing the VM, +// then it's safe to release handles in ctx_free. Default 0 = never touch Wren in destroy-notify. +#ifndef CLEAN_RELEASE +#define CLEAN_RELEASE 0 +#endif + +// Optional: mark VM alive/dead if you want to be precise. +// Keep static so callbacks can see it without host integration. +static bool g_vm_alive = true; + +// ---------- Foreign object wrapper ---------- +typedef struct { + GtkWidget* widget; // strong ref to GTK widget +} GtkObj; + +static void* gtkAllocate(WrenVM* vm) { + GtkObj* o = (GtkObj*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(GtkObj)); + o->widget = NULL; + return o; +} + +static void gtkFinalize(void* data) { + GtkObj* o = (GtkObj*)data; + if (o && o->widget) { + g_object_unref(o->widget); + o->widget = NULL; + } +} + +// Helpers +static GtkObj* get_self(WrenVM* vm) { + return (GtkObj*)wrenGetSlotForeign(vm, 0); +} +static GtkWidget* get_widget(WrenVM* vm) { + GtkObj* o = get_self(vm); + return o ? o->widget : NULL; +} +static GtkWidget* get_widget_from_slot(WrenVM* vm, int slot) { + GtkObj* other = (GtkObj*)wrenGetSlotForeign(vm, slot); + return other ? other->widget : NULL; +} + +// ---------- Callback plumbing ---------- +typedef struct { + WrenVM* vm; + WrenHandle* fn; // user's closure + WrenHandle* call0; // handle for "call()" +} CallbackCtx; + +static void ctx_free(gpointer data, GClosure* closure) { + CallbackCtx* ctx = (CallbackCtx*)data; + if (!ctx) return; +#if CLEAN_RELEASE + // Only safe if VM is still alive at this time. + if (g_vm_alive && ctx->vm) { + if (ctx->fn) wrenReleaseHandle(ctx->vm, ctx->fn); + if (ctx->call0) wrenReleaseHandle(ctx->vm, ctx->call0); + } +#endif + free(ctx); +} + +static void dispatch_noargs(GtkWidget* widget, gpointer user_data) { + CallbackCtx* ctx = (CallbackCtx*)user_data; + if (!ctx || !ctx->vm || !ctx->fn || !ctx->call0) return; + if (!g_vm_alive) return; // extra safety + wrenEnsureSlots(ctx->vm, 1); + wrenSetSlotHandle(ctx->vm, 0, ctx->fn); // receiver is the Fn + wrenCall(ctx->vm, ctx->call0); +} + +static CallbackCtx* make_ctx(WrenVM* vm, int slot_fn) { + CallbackCtx* ctx = (CallbackCtx*)calloc(1, sizeof(CallbackCtx)); + ctx->vm = vm; + ctx->fn = wrenGetSlotHandle(vm, slot_fn); + ctx->call0 = wrenMakeCallHandle(vm, "call()"); + return ctx; +} + +// ---------- Gtk (static) ---------- +static void gtkInit(WrenVM* vm) { + int argc = 0; char** argv = NULL; + // gtk_init will g_error() on failure (e.g., no DISPLAY) — that will abort process. + // If you need non-fatal behavior, switch to gtk_init_check. + if (!gtk_init_check(&argc, &argv)) { + // Fail silently to Wren; further GTK calls become no-ops via NULL checks. + // You can also wrenAbortFiber here if you prefer a hard error. + } + g_vm_alive = true; +} +static void gtkRun(WrenVM* vm) { gtk_main(); } +static void gtkQuit(WrenVM* vm){ gtk_main_quit(); g_vm_alive = false; } + +// ---------- Window ---------- +static void windowInit(WrenVM* vm) { + // init_(title, width, height) + const char* title = wrenGetSlotString(vm, 1); + int width = (int)wrenGetSlotDouble(vm, 2); + int height = (int)wrenGetSlotDouble(vm, 3); + + GtkObj* self = get_self(vm); + self->widget = gtk_window_new(GTK_WINDOW_TOPLEVEL); + if (!self->widget) return; + g_object_ref_sink(self->widget); + + gtk_window_set_title(GTK_WINDOW(self->widget), title ? title : ""); + gtk_window_set_default_size(GTK_WINDOW(self->widget), width, height); +} + +static void windowSetTitle(WrenVM* vm) { + GtkWidget* w = get_widget(vm); if (!w) return; + const char* title = wrenGetSlotString(vm, 1); + gtk_window_set_title(GTK_WINDOW(w), title ? title : ""); +} + +static void windowSetDefaultSize(WrenVM* vm) { + GtkWidget* w = get_widget(vm); if (!w) return; + int wi = (int)wrenGetSlotDouble(vm, 1); + int hi = (int)wrenGetSlotDouble(vm, 2); + gtk_window_set_default_size(GTK_WINDOW(w), wi, hi); +} + +static void windowAdd(WrenVM* vm) { + GtkWidget* w = get_widget(vm); if (!w) return; + GtkWidget* child = get_widget_from_slot(vm, 1); if (!child) return; + gtk_container_add(GTK_CONTAINER(w), child); +} + +static void windowShowAll(WrenVM* vm) { + GtkWidget* w = get_widget(vm); if (!w) return; + gtk_widget_show_all(w); +} + +static void windowOnDestroy(WrenVM* vm) { + GtkWidget* w = get_widget(vm); if (!w) return; + CallbackCtx* ctx = make_ctx(vm, 1); + g_signal_connect_data( + w, "destroy", + G_CALLBACK(dispatch_noargs), + ctx, ctx_free, 0); +} + +// ---------- Box ---------- +static void boxV(WrenVM* vm) { + int spacing = (int)wrenGetSlotDouble(vm, 1); + GtkObj* self = get_self(vm); + self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, spacing); + if (self->widget) g_object_ref_sink(self->widget); +} + +static void boxH(WrenVM* vm) { + int spacing = (int)wrenGetSlotDouble(vm, 1); + GtkObj* self = get_self(vm); + self->widget = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, spacing); + if (self->widget) g_object_ref_sink(self->widget); +} + +static void boxPackStart(WrenVM* vm) { + GtkWidget* w = get_widget(vm); if (!w) return; + GtkWidget* child = get_widget_from_slot(vm, 1); if (!child) return; + bool expand = wrenGetSlotBool(vm, 2); + bool fill = wrenGetSlotBool(vm, 3); + int padding = (int)wrenGetSlotDouble(vm, 4); + gtk_box_pack_start(GTK_BOX(w), child, expand, fill, padding); +} + +// ---------- Button ---------- +static void buttonInit(WrenVM* vm) { + const char* label = wrenGetSlotString(vm, 1); + GtkObj* self = get_self(vm); + self->widget = gtk_button_new_with_label(label ? label : ""); + if (self->widget) g_object_ref_sink(self->widget); +} + +static void buttonSetLabel(WrenVM* vm) { + GtkWidget* w = get_widget(vm); if (!w) return; + const char* label = wrenGetSlotString(vm, 1); + gtk_button_set_label(GTK_BUTTON(w), label ? label : ""); +} + +static void buttonOnClicked(WrenVM* vm) { + GtkWidget* w = get_widget(vm); if (!w) return; + CallbackCtx* ctx = make_ctx(vm, 1); + g_signal_connect_data( + w, "clicked", + G_CALLBACK(dispatch_noargs), + ctx, ctx_free, 0); +} + +// ---------- Entry ---------- +static void entryInit(WrenVM* vm) { + GtkObj* self = get_self(vm); + self->widget = gtk_entry_new(); + if (self->widget) g_object_ref_sink(self->widget); +} + +static void entrySetText(WrenVM* vm) { + GtkWidget* w = get_widget(vm); if (!w) return; + const char* text = wrenGetSlotString(vm, 1); + gtk_entry_set_text(GTK_ENTRY(w), text ? text : ""); +} + +static void entryGetText(WrenVM* vm) { + GtkWidget* w = get_widget(vm); + const char* s = (w ? gtk_entry_get_text(GTK_ENTRY(w)) : ""); + wrenSetSlotString(vm, 0, s ? s : ""); +} + +static void entryOnActivate(WrenVM* vm) { + GtkWidget* w = get_widget(vm); if (!w) return; + CallbackCtx* ctx = make_ctx(vm, 1); + g_signal_connect_data( + w, "activate", + G_CALLBACK(dispatch_noargs), + ctx, ctx_free, 0); +} + +static void entryOnChanged(WrenVM* vm) { + GtkWidget* w = get_widget(vm); if (!w) return; + CallbackCtx* ctx = make_ctx(vm, 1); + g_signal_connect_data( + w, "changed", + G_CALLBACK(dispatch_noargs), + ctx, ctx_free, 0); +} + +// ---------- Label ---------- +static void labelInit(WrenVM* vm) { + const char* text = wrenGetSlotString(vm, 1); + GtkObj* self = get_self(vm); + self->widget = gtk_label_new(text ? text : ""); + if (self->widget) g_object_ref_sink(self->widget); +} + +static void labelSetText(WrenVM* vm) { + GtkWidget* w = get_widget(vm); if (!w) return; + const char* text = wrenGetSlotString(vm, 1); + gtk_label_set_text(GTK_LABEL(w), text ? text : ""); +} + +// ---------- Binder (module "gtk") ---------- +WrenForeignMethodFn bindGtkForeignMethod(WrenVM* vm, const char* module, + const char* className, bool isStatic, const char* signature) +{ + if (strcmp(module, "gtk") != 0) return NULL; + + if (strcmp(className, "Gtk") == 0 && isStatic) { + if (strcmp(signature, "init_()") == 0) return gtkInit; + if (strcmp(signature, "run_()") == 0) return gtkRun; + if (strcmp(signature, "quit_()") == 0) return gtkQuit; + } + + if (strcmp(className, "Window") == 0 && !isStatic) { + if (strcmp(signature, "init_(_,_,_)") == 0) return windowInit; + if (strcmp(signature, "setTitle(_)") == 0) return windowSetTitle; + if (strcmp(signature, "setDefaultSize(_,_)")== 0) return windowSetDefaultSize; + if (strcmp(signature, "add(_)") == 0) return windowAdd; + if (strcmp(signature, "showAll()") == 0) return windowShowAll; + if (strcmp(signature, "onDestroy_(_)") == 0) return windowOnDestroy; + } + + if (strcmp(className, "Box") == 0 && !isStatic) { + if (strcmp(signature, "vbox_(_)") == 0) return boxV; + if (strcmp(signature, "hbox_(_)") == 0) return boxH; + if (strcmp(signature, "packStart(_,_,_,_)") == 0) return boxPackStart; + } + + if (strcmp(className, "Button") == 0 && !isStatic) { + if (strcmp(signature, "init_(_)") == 0) return buttonInit; + if (strcmp(signature, "setLabel(_)") == 0) return buttonSetLabel; + if (strcmp(signature, "onClicked_(_)") == 0) return buttonOnClicked; + } + + if (strcmp(className, "Entry") == 0 && !isStatic) { + if (strcmp(signature, "init_()") == 0) return entryInit; + if (strcmp(signature, "setText(_)") == 0) return entrySetText; + if (strcmp(signature, "text") == 0) return entryGetText; + if (strcmp(signature, "onActivate_(_)") == 0) return entryOnActivate; + if (strcmp(signature, "onChanged_(_)") == 0) return entryOnChanged; + } + + if (strcmp(className, "Label") == 0 && !isStatic) { + if (strcmp(signature, "init_(_)") == 0) return labelInit; + if (strcmp(signature, "setText(_)") == 0) return labelSetText; + } + + return NULL; +} + +WrenForeignClassMethods bindGtkForeignClass(WrenVM* vm, const char* module, const char* className) { + WrenForeignClassMethods methods = (WrenForeignClassMethods){0, 0}; + if (strcmp(module, "gtk") == 0) { + if (!strcmp(className, "Window") || + !strcmp(className, "Box") || + !strcmp(className, "Button") || + !strcmp(className, "Entry") || + !strcmp(className, "Label")) + { + methods.allocate = gtkAllocate; + methods.finalize = gtkFinalize; + } + // Gtk (static) carries no foreign storage. + } + return methods; +} + diff --git a/gtk_example.wren b/gtk_example.wren new file mode 100644 index 0000000..101c9c7 --- /dev/null +++ b/gtk_example.wren @@ -0,0 +1,40 @@ +// main.wren +import "gtk" for Gtk, Window, Box, Button, Entry, Label + +// Optional: your host can still expose this to quit if needed, but not required here +// foreign class Host { foreign static signalDone() } + +var ui = Fiber.new { + Gtk.init() + + var win = Window.new("Wren + GTK", 420, 200) + var root = Box.vbox(8) + var info = Label.new("Type something and click the button:") + var entry = Entry.new() + var btn = Button.new("Say hi") + + root.packStart(info, false, false, 0) + root.packStart(entry, false, false, 0) + root.packStart(btn, false, false, 0) + + win.add(root) + + // Quit the app when the window is closed + win.onDestroy { Gtk.quit() } + + // Update the label with the current entry text + btn.onClicked { + info.setText("You typed: " + entry.text) + } + + // Also submit on Enter key in the entry + entry.onActivate { + info.setText("Enter pressed: " + entry.text) + } + + win.showAll() + Gtk.run() + // Host.signalDone() // if you want to tell your C host to exit +} + +ui.call() diff --git a/main.c b/main.c index ca2ebe4..5976e08 100644 --- a/main.c +++ b/main.c @@ -19,7 +19,8 @@ #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; @@ -78,6 +79,57 @@ static WrenLoadModuleResult loadModule(WrenVM* vm, const char* name) { 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; @@ -102,6 +154,11 @@ WrenForeignMethodFn combinedBindForeignMethod(WrenVM* vm, const char* module, co 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; } @@ -122,7 +179,11 @@ WrenForeignClassMethods combinedBindForeignClass(WrenVM* vm, const char* module, WrenForeignClassMethods methods = {0, 0}; return methods; } + if (strcmp(module, "gtk") == 0){ + return bindForeignClass(vm, module, className); + } WrenForeignClassMethods methods = {0, 0}; + return methods; } @@ -182,6 +243,8 @@ int main(int argc, char* argv[]) { // === Main Event Loop === while (!g_mainFiberIsDone) { // ** Process completions for ALL managers ** + bool had_work = true; + socketManager_processCompletions(); httpManager_processCompletions(); dbManager_processCompletions(); @@ -194,20 +257,23 @@ int main(int argc, char* argv[]) { 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(1000); // 1ms + usleep(1); // 1ms #endif + } } // Process any final completions before shutting down - socketManager_processCompletions(); - httpManager_processCompletions(); - dbManager_processCompletions(); - wrenReleaseHandle(vm, mainFiberHandle); wrenReleaseHandle(vm, callHandle); diff --git a/merged_source_files.txt b/merged_source_files.txt deleted file mode 100644 index e694c9f..0000000 --- a/merged_source_files.txt +++ /dev/null @@ -1,15538 +0,0 @@ -// Start of sqlite3_backend.c -#include "wren.h" -#include -#include -#include -#include -#include - -#ifdef _WIN32 - #include - typedef HANDLE thread_t; - typedef CRITICAL_SECTION mutex_t; - typedef CONDITION_VARIABLE cond_t; -#else - #include - typedef pthread_t thread_t; - typedef pthread_mutex_t mutex_t; - typedef pthread_cond_t cond_t; -#endif - -// --- Data Structures --- - -typedef enum { - DB_OP_OPEN, - DB_OP_EXEC, - DB_OP_QUERY, - DB_OP_CLOSE -} DBOp; - -typedef struct { - sqlite3* db; -} DatabaseData; - -// C-side representation of query results to pass from worker to main thread. -typedef struct DbValue { - int type; - union { - double num; - struct { - char* text; - int length; - } str; - } as; -} DbValue; - -typedef struct DbRow { - char** columns; - DbValue* values; - int count; - struct DbRow* next; -} DbRow; - -typedef struct DbContext { - WrenVM* vm; - DBOp operation; - WrenHandle* callback; - WrenHandle* dbHandle; - char* path; - sqlite3* newDb; - char* sql; - sqlite3* db; - bool success; - char* errorMessage; - DbRow* resultRows; - struct DbContext* next; -} DbContext; - -// --- Thread-Safe Queue --- - -typedef struct { - DbContext *head, *tail; - mutex_t mutex; - cond_t cond; -} DbThreadSafeQueue; - -void db_queue_init(DbThreadSafeQueue* q) { - q->head = q->tail = NULL; - #ifdef _WIN32 - InitializeCriticalSection(&q->mutex); - InitializeConditionVariable(&q->cond); - #else - pthread_mutex_init(&q->mutex, NULL); - pthread_cond_init(&q->cond, NULL); - #endif -} - -void db_queue_destroy(DbThreadSafeQueue* q) { - #ifdef _WIN32 - DeleteCriticalSection(&q->mutex); - #else - pthread_mutex_destroy(&q->mutex); - pthread_cond_destroy(&q->cond); - #endif -} - -void db_queue_push(DbThreadSafeQueue* q, DbContext* context) { - #ifdef _WIN32 - EnterCriticalSection(&q->mutex); - #else - pthread_mutex_lock(&q->mutex); - #endif - if(context) context->next = NULL; - if (q->tail) q->tail->next = context; - else q->head = context; - q->tail = context; - #ifdef _WIN32 - WakeConditionVariable(&q->cond); - LeaveCriticalSection(&q->mutex); - #else - pthread_cond_signal(&q->cond); - pthread_mutex_unlock(&q->mutex); - #endif -} - -DbContext* db_queue_pop(DbThreadSafeQueue* q) { - #ifdef _WIN32 - EnterCriticalSection(&q->mutex); - while (q->head == NULL) { - SleepConditionVariableCS(&q->cond, &q->mutex, INFINITE); - } - #else - pthread_mutex_lock(&q->mutex); - while (q->head == NULL) { - pthread_cond_wait(&q->cond, &q->mutex); - } - #endif - DbContext* context = q->head; - q->head = q->head->next; - if (q->head == NULL) q->tail = NULL; - #ifdef _WIN32 - LeaveCriticalSection(&q->mutex); - #else - pthread_mutex_unlock(&q->mutex); - #endif - return context; -} - -bool db_queue_empty(DbThreadSafeQueue* q) { - bool empty; - #ifdef _WIN32 - EnterCriticalSection(&q->mutex); - empty = (q->head == NULL); - LeaveCriticalSection(&q->mutex); - #else - pthread_mutex_lock(&q->mutex); - empty = (q->head == NULL); - pthread_mutex_unlock(&q->mutex); - #endif - return empty; -} - -// --- Async DB Manager --- - -typedef struct { - WrenVM* vm; - volatile bool running; - thread_t threads[2]; - DbThreadSafeQueue requestQueue; - DbThreadSafeQueue completionQueue; -} AsyncDbManager; - -static AsyncDbManager* dbManager = NULL; - -void free_db_result_rows(DbRow* rows) { - while (rows) { - DbRow* next = rows->next; - if (rows->columns) { - for (int i = 0; i < rows->count; i++) { - free(rows->columns[i]); - } - free(rows->columns); - } - if (rows->values) { - for (int i = 0; i < rows->count; i++) { - if (rows->values[i].type == SQLITE_TEXT || rows->values[i].type == SQLITE_BLOB) { - free(rows->values[i].as.str.text); - } - } - free(rows->values); - } - free(rows); - rows = next; - } -} - -void free_db_context(DbContext* context) { - if (context == NULL) return; - free(context->path); - free(context->sql); - free(context->errorMessage); - if (context->dbHandle) wrenReleaseHandle(context->vm, context->dbHandle); - if (context->callback) wrenReleaseHandle(context->vm, context->callback); - if (context->resultRows) free_db_result_rows(context->resultRows); - free(context); -} - -static void set_context_error(DbContext* context, const char* message) { - if (context == NULL) return; - context->success = false; - if (context->errorMessage) free(context->errorMessage); - context->errorMessage = message ? strdup(message) : strdup("An unknown database error occurred."); -} - -#ifdef _WIN32 -DWORD WINAPI dbWorkerThread(LPVOID arg); -#else -void* dbWorkerThread(void* arg); -#endif - -void dbManager_create(WrenVM* vm) { - if (dbManager != NULL) return; - dbManager = (AsyncDbManager*)malloc(sizeof(AsyncDbManager)); - if (dbManager == NULL) return; - dbManager->vm = vm; - dbManager->running = true; - db_queue_init(&dbManager->requestQueue); - db_queue_init(&dbManager->completionQueue); - for (int i = 0; i < 2; ++i) { - #ifdef _WIN32 - dbManager->threads[i] = CreateThread(NULL, 0, dbWorkerThread, dbManager, 0, NULL); - #else - pthread_create(&dbManager->threads[i], NULL, dbWorkerThread, dbManager); - #endif - } -} - -void dbManager_destroy() { - if (!dbManager) return; - dbManager->running = false; - for (int i = 0; i < 2; ++i) db_queue_push(&dbManager->requestQueue, NULL); - for (int i = 0; i < 2; ++i) { - #ifdef _WIN32 - WaitForSingleObject(dbManager->threads[i], INFINITE); - CloseHandle(dbManager->threads[i]); - #else - pthread_join(dbManager->threads[i], NULL); - #endif - } - while(!db_queue_empty(&dbManager->requestQueue)) free_db_context(db_queue_pop(&dbManager->requestQueue)); - while(!db_queue_empty(&dbManager->completionQueue)) free_db_context(db_queue_pop(&dbManager->completionQueue)); - db_queue_destroy(&dbManager->requestQueue); - db_queue_destroy(&dbManager->completionQueue); - free(dbManager); - dbManager = NULL; -} - -void dbManager_processCompletions() { - if (!dbManager || !dbManager->vm || db_queue_empty(&dbManager->completionQueue)) return; - - while (!db_queue_empty(&dbManager->completionQueue)) { - DbContext* context = db_queue_pop(&dbManager->completionQueue); - if (context == NULL) continue; - - if (context->success && context->dbHandle) { - wrenEnsureSlots(dbManager->vm, 1); - wrenSetSlotHandle(dbManager->vm, 0, context->dbHandle); - DatabaseData* dbData = (DatabaseData*)wrenGetSlotForeign(dbManager->vm, 0); - if (dbData) { - if (context->operation == DB_OP_OPEN) dbData->db = context->newDb; - else if (context->operation == DB_OP_CLOSE) dbData->db = NULL; - } - } - - if (context->callback == NULL) { - free_db_context(context); - continue; - } - - WrenHandle* callHandle = NULL; - int numArgs = 0; - if (context->operation == DB_OP_QUERY) { - callHandle = wrenMakeCallHandle(dbManager->vm, "call(_,_)"); - numArgs = 2; - } else { - callHandle = wrenMakeCallHandle(dbManager->vm, "call(_)"); - numArgs = 1; - } - - if (callHandle == NULL) { - free_db_context(context); - continue; - } - - // Ensure enough slots for callback, args, and temp work. - // Slots 0, 1, 2 are for the callback and its arguments. - // Slots 3, 4, 5 are for temporary work building maps. - wrenEnsureSlots(dbManager->vm, 6); - wrenSetSlotHandle(dbManager->vm, 0, context->callback); - - if (context->success) { - wrenSetSlotNull(dbManager->vm, 1); // error is null - if (numArgs == 2) { // Query case - if (context->resultRows) { - wrenSetSlotNewList(dbManager->vm, 2); // Result list in slot 2 - DbRow* row = context->resultRows; - while(row) { - wrenSetSlotNewMap(dbManager->vm, 3); // Temp map for row in slot 3 - for (int i = 0; i < row->count; i++) { - // Use slots 4 and 5 for key/value to avoid conflicts - wrenSetSlotString(dbManager->vm, 4, row->columns[i]); - DbValue* val = &row->values[i]; - switch (val->type) { - case SQLITE_INTEGER: case SQLITE_FLOAT: - wrenSetSlotDouble(dbManager->vm, 5, val->as.num); break; - case SQLITE_TEXT: case SQLITE_BLOB: - wrenSetSlotBytes(dbManager->vm, 5, val->as.str.text, val->as.str.length); break; - case SQLITE_NULL: - wrenSetSlotNull(dbManager->vm, 5); break; - } - wrenSetMapValue(dbManager->vm, 3, 4, 5); // map=3, key=4, val=5 - } - wrenInsertInList(dbManager->vm, 2, -1, 3); // list=2, element=3 - row = row->next; - } - } else { - wrenSetSlotNewList(dbManager->vm, 2); // Return empty list for success with no rows - } - } - } else { - wrenSetSlotString(dbManager->vm, 1, context->errorMessage ? context->errorMessage : "Unknown error."); - if (numArgs == 2) wrenSetSlotNull(dbManager->vm, 2); - } - - wrenCall(dbManager->vm, callHandle); - wrenReleaseHandle(dbManager->vm, callHandle); - free_db_context(context); - } -} - -// --- Worker Thread --- -#ifdef _WIN32 -DWORD WINAPI dbWorkerThread(LPVOID arg) { -#else -void* dbWorkerThread(void* arg) { -#endif - AsyncDbManager* manager = (AsyncDbManager*)arg; - while (manager->running) { - DbContext* context = db_queue_pop(&manager->requestQueue); - if (!context || !manager->running) { - if (context) free_db_context(context); - break; - } - switch (context->operation) { - case DB_OP_OPEN: { - int rc = sqlite3_open_v2(context->path, &context->newDb, - SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, - NULL); - if (rc != SQLITE_OK) { - set_context_error(context, sqlite3_errmsg(context->newDb)); - sqlite3_close(context->newDb); - context->newDb = NULL; - } else { - context->success = true; - } - break; - } - case DB_OP_EXEC: { - if (!context->db) { set_context_error(context, "Database is not open."); break; } - char* err = NULL; - int rc = sqlite3_exec(context->db, context->sql, 0, 0, &err); - if (rc != SQLITE_OK) { - set_context_error(context, err); - sqlite3_free(err); - } else { - context->success = true; - } - break; - } - case DB_OP_QUERY: { - if (!context->db) { set_context_error(context, "Database is not open."); break; } - sqlite3_stmt* stmt; - int rc = sqlite3_prepare_v2(context->db, context->sql, -1, &stmt, 0); - if (rc != SQLITE_OK) { set_context_error(context, sqlite3_errmsg(context->db)); break; } - - int colCount = sqlite3_column_count(stmt); - DbRow* head = NULL, *tail = NULL; - bool oom = false; - while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { - DbRow* row = (DbRow*)calloc(1, sizeof(DbRow)); - if (!row) { oom = true; break; } - row->count = colCount; - row->columns = (char**)malloc(sizeof(char*) * colCount); - row->values = (DbValue*)malloc(sizeof(DbValue) * colCount); - if (!row->columns || !row->values) { free(row->columns); free(row->values); free(row); oom = true; break; } - for (int i = 0; i < colCount; i++) { - const char* colName = sqlite3_column_name(stmt, i); - row->columns[i] = colName ? strdup(colName) : strdup(""); - if (!row->columns[i]) { for (int j=0; jcolumns[j]); free(row->columns); free(row->values); free(row); oom = true; goto query_loop_end; } - DbValue* val = &row->values[i]; - val->type = sqlite3_column_type(stmt, i); - switch (val->type) { - case SQLITE_INTEGER: case SQLITE_FLOAT: val->as.num = sqlite3_column_double(stmt, i); break; - case SQLITE_TEXT: case SQLITE_BLOB: { - const void* blob = sqlite3_column_blob(stmt, i); - int len = sqlite3_column_bytes(stmt, i); - val->as.str.text = (char*)malloc(len); - if (!val->as.str.text) { for (int j=0; j<=i; j++) free(row->columns[j]); free(row->columns); free(row->values); free(row); oom = true; goto query_loop_end; } - memcpy(val->as.str.text, blob, len); - val->as.str.length = len; - break; - } - case SQLITE_NULL: break; - } - } - if (!head) head = tail = row; else { tail->next = row; tail = row; } - } - query_loop_end:; - if (oom) { set_context_error(context, "Out of memory during query."); free_db_result_rows(head); } - else if (rc != SQLITE_DONE) { set_context_error(context, sqlite3_errmsg(context->db)); free_db_result_rows(head); } - else { context->success = true; context->resultRows = head; } - sqlite3_finalize(stmt); - break; - } - case DB_OP_CLOSE: { - if (context->db) sqlite3_close(context->db); - context->success = true; - break; - } - } - db_queue_push(&manager->completionQueue, context); - } - return 0; -} - -// --- Wren FFI --- - -void dbAllocate(WrenVM* vm) { - DatabaseData* data = (DatabaseData*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(DatabaseData)); - if (data) data->db = NULL; -} - -void dbFinalize(void* data) { - DatabaseData* dbData = (DatabaseData*)data; - if (dbData && dbData->db) sqlite3_close(dbData->db); -} - -static void create_db_context(WrenVM* vm, DBOp op, int sqlSlot, int cbSlot) { - DbContext* context = (DbContext*)calloc(1, sizeof(DbContext)); - if (!context) { wrenSetSlotString(vm, 0, "Out of memory."); wrenAbortFiber(vm, 0); return; } - context->vm = vm; - context->operation = op; - context->dbHandle = wrenGetSlotHandle(vm, 0); - context->callback = wrenGetSlotHandle(vm, cbSlot); - if (sqlSlot != -1) { - if (wrenGetSlotType(vm, sqlSlot) != WREN_TYPE_STRING) { - wrenSetSlotString(vm, 0, "SQL argument must be a string."); - wrenAbortFiber(vm, 0); - free_db_context(context); - return; - } - const char* sql_str = wrenGetSlotString(vm, sqlSlot); - if (sql_str) context->sql = strdup(sql_str); - if (!context->sql) { set_context_error(context, "Out of memory."); db_queue_push(&dbManager->requestQueue, context); return; } - } - DatabaseData* dbData = (DatabaseData*)wrenGetSlotForeign(vm, 0); - if (!dbData) { set_context_error(context, "Invalid database object."); db_queue_push(&dbManager->requestQueue, context); return; } - context->db = dbData->db; - db_queue_push(&dbManager->requestQueue, context); -} - -void dbOpen(WrenVM* vm) { - DbContext* context = (DbContext*)calloc(1, sizeof(DbContext)); - if (!context) { wrenSetSlotString(vm, 0, "Out of memory."); wrenAbortFiber(vm, 0); return; } - context->vm = vm; - context->operation = DB_OP_OPEN; - const char* path_str = wrenGetSlotString(vm, 1); - if (path_str) context->path = strdup(path_str); - if (!context->path) { free(context); wrenSetSlotString(vm, 0, "Out of memory."); wrenAbortFiber(vm, 0); return; } - context->dbHandle = wrenGetSlotHandle(vm, 0); - context->callback = wrenGetSlotHandle(vm, 2); - db_queue_push(&dbManager->requestQueue, context); -} - -void dbExec(WrenVM* vm) { create_db_context(vm, DB_OP_EXEC, 1, 2); } -void dbQuery(WrenVM* vm) { create_db_context(vm, DB_OP_QUERY, 1, 2); } -void dbClose(WrenVM* vm) { create_db_context(vm, DB_OP_CLOSE, -1, 1); } - -WrenForeignMethodFn bindSqliteForeignMethod(WrenVM* vm, const char* module, const char* className, bool isStatic, const char* signature) { - if (strcmp(module, "sqlite") != 0) return NULL; - if (strcmp(className, "Database") == 0 && !isStatic) { - if (strcmp(signature, "open_(_,_)") == 0) return dbOpen; - if (strcmp(signature, "exec_(_,_)") == 0) return dbExec; - if (strcmp(signature, "query_(_,_)") == 0) return dbQuery; - if (strcmp(signature, "close_(_)") == 0) return dbClose; - } - return NULL; -} - -WrenForeignClassMethods bindSqliteForeignClass(WrenVM* vm, const char* module, const char* className) { - if (strcmp(module, "sqlite") == 0 && strcmp(className, "Database") == 0) { - WrenForeignClassMethods methods = {dbAllocate, dbFinalize}; - return methods; - } - WrenForeignClassMethods methods = {0, 0}; - return methods; -} - - -// End of sqlite3_backend.c - -// Start of main.c -#include -#include -#include -#include -#include - -#ifdef _WIN32 - #include -#else - #include -#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" - -// --- 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 (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, "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; - } - WrenForeignClassMethods methods = {0, 0}; - return methods; -} - - -// --- Main Application Entry Point --- -int main(int argc, char* argv[]) { - if (argc < 2) { - fprintf(stderr, "Usage: %s \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 ** - 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; - } - - // Prevent 100% CPU usage - #ifdef _WIN32 - Sleep(1); - #else - usleep(1000); // 1ms - #endif - } - - // Process any final completions before shutting down - socketManager_processCompletions(); - httpManager_processCompletions(); - dbManager_processCompletions(); - - 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; -} - - -// End of main.c - -// Start of requests_backend.c -#include "wren.h" -#include -#include -#include -#include - -#ifdef _WIN32 - #include - typedef HANDLE thread_t; - typedef CRITICAL_SECTION mutex_t; - typedef CONDITION_VARIABLE cond_t; -#else - #include - typedef pthread_t thread_t; - typedef pthread_mutex_t mutex_t; - typedef pthread_cond_t cond_t; -#endif - -// --- Data Structures --- - -typedef struct { - int isError; - long statusCode; - char* body; - size_t body_len; -} ResponseData; - -typedef struct { - char* memory; - size_t size; -} MemoryStruct; - -typedef struct HttpContext { - WrenVM* vm; - WrenHandle* callback; - - char* url; - char* method; - char* body; - struct curl_slist* headers; - - bool success; - char* response_body; - size_t response_body_len; - long status_code; - char* error_message; - struct HttpContext* next; -} HttpContext; - - -// --- Thread-Safe Queue --- - -typedef struct { - HttpContext *head, *tail; - mutex_t mutex; - cond_t cond; -} ThreadSafeQueue; - -void http_queue_init(ThreadSafeQueue* q) { - q->head = q->tail = NULL; - #ifdef _WIN32 - InitializeCriticalSection(&q->mutex); - InitializeConditionVariable(&q->cond); - #else - pthread_mutex_init(&q->mutex, NULL); - pthread_cond_init(&q->cond, NULL); - #endif -} - -void http_queue_destroy(ThreadSafeQueue* q) { - #ifdef _WIN32 - DeleteCriticalSection(&q->mutex); - #else - pthread_mutex_destroy(&q->mutex); - pthread_cond_destroy(&q->cond); - #endif -} - -void http_queue_push(ThreadSafeQueue* q, HttpContext* context) { - #ifdef _WIN32 - EnterCriticalSection(&q->mutex); - #else - pthread_mutex_lock(&q->mutex); - #endif - - if(context) context->next = NULL; - if (q->tail) q->tail->next = context; - else q->head = context; - q->tail = context; - - #ifdef _WIN32 - WakeConditionVariable(&q->cond); - LeaveCriticalSection(&q->mutex); - #else - pthread_cond_signal(&q->cond); - pthread_mutex_unlock(&q->mutex); - #endif -} - -HttpContext* http_queue_pop(ThreadSafeQueue* q) { - #ifdef _WIN32 - EnterCriticalSection(&q->mutex); - while (q->head == NULL) { - SleepConditionVariableCS(&q->cond, &q->mutex, INFINITE); - } - #else - pthread_mutex_lock(&q->mutex); - while (q->head == NULL) { - pthread_cond_wait(&q->cond, &q->mutex); - } - #endif - - HttpContext* context = q->head; - q->head = q->head->next; - if (q->head == NULL) q->tail = NULL; - - #ifdef _WIN32 - LeaveCriticalSection(&q->mutex); - #else - pthread_mutex_unlock(&q->mutex); - #endif - - return context; -} - -bool http_queue_empty(ThreadSafeQueue* q) { - #ifdef _WIN32 - EnterCriticalSection(&q->mutex); - bool empty = (q->head == NULL); - LeaveCriticalSection(&q->mutex); - #else - pthread_mutex_lock(&q->mutex); - bool empty = (q->head == NULL); - pthread_mutex_unlock(&q->mutex); - #endif - return empty; -} - - -// --- libcurl Helpers --- -static size_t write_memory_callback(void *contents, size_t size, size_t nmemb, void *userp) { - size_t realsize = size * nmemb; - MemoryStruct *mem = (MemoryStruct *)userp; - char *ptr = (char*)realloc(mem->memory, mem->size + realsize + 1); - if (ptr == NULL) return 0; - mem->memory = ptr; - memcpy(&(mem->memory[mem->size]), contents, realsize); - mem->size += realsize; - mem->memory[mem->size] = 0; - return realsize; -} - -// --- Async HTTP Manager --- - -typedef struct { - WrenVM* vm; - volatile bool running; - thread_t threads[4]; - ThreadSafeQueue requestQueue; - ThreadSafeQueue completionQueue; -} AsyncHttpManager; - -static AsyncHttpManager* httpManager = NULL; - -void free_http_context(HttpContext* context) { - if (!context) return; - free(context->url); - free(context->method); - free(context->body); - curl_slist_free_all(context->headers); - free(context->response_body); - free(context->error_message); - free(context); -} - -#ifdef _WIN32 -DWORD WINAPI httpWorkerThread(LPVOID arg) { -#else -void* httpWorkerThread(void* arg) { -#endif - AsyncHttpManager* manager = (AsyncHttpManager*)arg; - while (manager->running) { - HttpContext* context = http_queue_pop(&manager->requestQueue); - if (!context || !manager->running) { - if (context) free_http_context(context); - break; - } - - CURL *curl = curl_easy_init(); - if (curl) { - MemoryStruct chunk; - chunk.memory = (char*)malloc(1); - chunk.size = 0; - - curl_easy_setopt(curl, CURLOPT_URL, context->url); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_memory_callback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk); - curl_easy_setopt(curl, CURLOPT_USERAGENT, "wren-curl-agent/1.0"); - - if (strcmp(context->method, "POST") == 0) { - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, context->body); - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, context->headers); - } - - CURLcode res = curl_easy_perform(curl); - - if (res == CURLE_OK) { - context->success = true; - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &context->status_code); - context->response_body = chunk.memory; - context->response_body_len = chunk.size; - } else { - context->success = false; - context->status_code = -1; - context->error_message = strdup(curl_easy_strerror(res)); - free(chunk.memory); - } - curl_easy_cleanup(curl); - } else { - context->success = false; - context->error_message = strdup("Failed to initialize cURL handle."); - } - http_queue_push(&manager->completionQueue, context); - } - return 0; -} - -void httpManager_create(WrenVM* vm) { - httpManager = (AsyncHttpManager*)malloc(sizeof(AsyncHttpManager)); - httpManager->vm = vm; - httpManager->running = true; - http_queue_init(&httpManager->requestQueue); - http_queue_init(&httpManager->completionQueue); - for (int i = 0; i < 4; ++i) { - #ifdef _WIN32 - httpManager->threads[i] = CreateThread(NULL, 0, httpWorkerThread, httpManager, 0, NULL); - #else - pthread_create(&httpManager->threads[i], NULL, httpWorkerThread, httpManager); - #endif - } -} - -void httpManager_destroy() { - httpManager->running = false; - for (int i = 0; i < 4; ++i) { - http_queue_push(&httpManager->requestQueue, NULL); - } - for (int i = 0; i < 4; ++i) { - #ifdef _WIN32 - WaitForSingleObject(httpManager->threads[i], INFINITE); - CloseHandle(httpManager->threads[i]); - #else - pthread_join(httpManager->threads[i], NULL); - #endif - } - http_queue_destroy(&httpManager->requestQueue); - http_queue_destroy(&httpManager->completionQueue); - free(httpManager); -} - -void httpManager_processCompletions() { - while (!http_queue_empty(&httpManager->completionQueue)) { - HttpContext* context = http_queue_pop(&httpManager->completionQueue); - - WrenHandle* callHandle = wrenMakeCallHandle(httpManager->vm, "call(_,_)"); - wrenEnsureSlots(httpManager->vm, 3); - wrenSetSlotHandle(httpManager->vm, 0, context->callback); - - if (context->success) { - wrenSetSlotNull(httpManager->vm, 1); - - wrenGetVariable(httpManager->vm, "requests", "Response", 2); - void* foreign = wrenSetSlotNewForeign(httpManager->vm, 2, 2, sizeof(ResponseData)); - ResponseData* data = (ResponseData*)foreign; - data->isError = false; - data->statusCode = context->status_code; - data->body = context->response_body; - data->body_len = context->response_body_len; - context->response_body = NULL; - } else { - wrenSetSlotString(httpManager->vm, 1, context->error_message); - wrenSetSlotNull(httpManager->vm, 2); - } - - wrenCall(httpManager->vm, callHandle); - wrenReleaseHandle(httpManager->vm, context->callback); - wrenReleaseHandle(httpManager->vm, callHandle); - free_http_context(context); - } -} - -void httpManager_submit(HttpContext* context) { - http_queue_push(&httpManager->requestQueue, context); -} - -// --- Wren Foreign Methods --- - -void responseFinalize(void* data) { - ResponseData* response = (ResponseData*)data; - free(response->body); -} - -void responseAllocate(WrenVM* vm) { - ResponseData* data = (ResponseData*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(ResponseData)); - data->isError = 0; - data->statusCode = 0; - data->body = NULL; - data->body_len = 0; -} - -void responseIsError(WrenVM* vm) { - ResponseData* data = (ResponseData*)wrenGetSlotForeign(vm, 0); - wrenSetSlotBool(vm, 0, data->isError ? true : false); -} - -void responseStatusCode(WrenVM* vm) { - ResponseData* data = (ResponseData*)wrenGetSlotForeign(vm, 0); - wrenSetSlotDouble(vm, 0, (double)data->statusCode); -} - -void responseBody(WrenVM* vm) { - ResponseData* data = (ResponseData*)wrenGetSlotForeign(vm, 0); - wrenSetSlotBytes(vm, 0, data->body ? data->body : "", data->body_len); -} - -void responseJson(WrenVM* vm) { - // CORRECTED: Replaced incorrect call with the actual logic. - ResponseData* data = (ResponseData*)wrenGetSlotForeign(vm, 0); - wrenSetSlotBytes(vm, 0, data->body ? data->body : "", data->body_len); -} - -void requestsGet(WrenVM* vm) { - HttpContext* context = (HttpContext*)calloc(1, sizeof(HttpContext)); - context->vm = vm; - context->method = strdup("GET"); - context->url = strdup(wrenGetSlotString(vm, 1)); - context->callback = wrenGetSlotHandle(vm, 3); - httpManager_submit(context); -} - -void requestsPost(WrenVM* vm) { - HttpContext* context = (HttpContext*)calloc(1, sizeof(HttpContext)); - context->vm = vm; - context->method = strdup("POST"); - context->url = strdup(wrenGetSlotString(vm, 1)); - context->body = strdup(wrenGetSlotString(vm, 2)); - const char* contentType = wrenGetSlotString(vm, 3); - char contentTypeHeader[256]; - snprintf(contentTypeHeader, sizeof(contentTypeHeader), "Content-Type: %s", contentType); - context->headers = curl_slist_append(NULL, contentTypeHeader); - context->callback = wrenGetSlotHandle(vm, 5); - httpManager_submit(context); -} - -// --- FFI Binding Functions --- - -WrenForeignMethodFn bindForeignMethod(WrenVM* vm, const char* module, - const char* className, bool isStatic, const char* signature) { - if (strcmp(module, "requests") != 0) return NULL; - - if (strcmp(className, "Requests") == 0 && isStatic) { - if (strcmp(signature, "get_(_,_,_)") == 0) return requestsGet; - if (strcmp(signature, "post_(_,_,_,_,_)") == 0) return requestsPost; - } - - if (strcmp(className, "Response") == 0 && !isStatic) { - if (strcmp(signature, "isError") == 0) return responseIsError; - if (strcmp(signature, "statusCode") == 0) return responseStatusCode; - if (strcmp(signature, "body") == 0) return responseBody; - if (strcmp(signature, "json()") == 0) return responseJson; - } - - return NULL; -} - -WrenForeignClassMethods bindForeignClass(WrenVM* vm, const char* module, const char* className) { - WrenForeignClassMethods methods = {0, 0}; - if (strcmp(module, "requests") == 0) { - if (strcmp(className, "Response") == 0) { - methods.allocate = responseAllocate; - methods.finalize = responseFinalize; - } - } - return methods; -} - -// End of requests_backend.c - -// Start of wren.h -#ifndef wren_h -#define wren_h - -#include -#include -#include - -// The Wren semantic version number components. -#define WREN_VERSION_MAJOR 0 -#define WREN_VERSION_MINOR 4 -#define WREN_VERSION_PATCH 0 - -// A human-friendly string representation of the version. -#define WREN_VERSION_STRING "0.4.0" - -// A monotonically increasing numeric representation of the version number. Use -// this if you want to do range checks over versions. -#define WREN_VERSION_NUMBER (WREN_VERSION_MAJOR * 1000000 + \ - WREN_VERSION_MINOR * 1000 + \ - WREN_VERSION_PATCH) - -#ifndef WREN_API - #if defined(_MSC_VER) && defined(WREN_API_DLLEXPORT) - #define WREN_API __declspec( dllexport ) - #else - #define WREN_API - #endif -#endif //WREN_API - -// A single virtual machine for executing Wren code. -// -// Wren has no global state, so all state stored by a running interpreter lives -// here. -typedef struct WrenVM WrenVM; - -// A handle to a Wren object. -// -// This lets code outside of the VM hold a persistent reference to an object. -// After a handle is acquired, and until it is released, this ensures the -// garbage collector will not reclaim the object it references. -typedef struct WrenHandle WrenHandle; - -// A generic allocation function that handles all explicit memory management -// used by Wren. It's used like so: -// -// - To allocate new memory, [memory] is NULL and [newSize] is the desired -// size. It should return the allocated memory or NULL on failure. -// -// - To attempt to grow an existing allocation, [memory] is the memory, and -// [newSize] is the desired size. It should return [memory] if it was able to -// grow it in place, or a new pointer if it had to move it. -// -// - To shrink memory, [memory] and [newSize] are the same as above but it will -// always return [memory]. -// -// - To free memory, [memory] will be the memory to free and [newSize] will be -// zero. It should return NULL. -typedef void* (*WrenReallocateFn)(void* memory, size_t newSize, void* userData); - -// A function callable from Wren code, but implemented in C. -typedef void (*WrenForeignMethodFn)(WrenVM* vm); - -// A finalizer function for freeing resources owned by an instance of a foreign -// class. Unlike most foreign methods, finalizers do not have access to the VM -// and should not interact with it since it's in the middle of a garbage -// collection. -typedef void (*WrenFinalizerFn)(void* data); - -// Gives the host a chance to canonicalize the imported module name, -// potentially taking into account the (previously resolved) name of the module -// that contains the import. Typically, this is used to implement relative -// imports. -typedef const char* (*WrenResolveModuleFn)(WrenVM* vm, - const char* importer, const char* name); - -// Forward declare -struct WrenLoadModuleResult; - -// Called after loadModuleFn is called for module [name]. The original returned result -// is handed back to you in this callback, so that you can free memory if appropriate. -typedef void (*WrenLoadModuleCompleteFn)(WrenVM* vm, const char* name, struct WrenLoadModuleResult result); - -// The result of a loadModuleFn call. -// [source] is the source code for the module, or NULL if the module is not found. -// [onComplete] an optional callback that will be called once Wren is done with the result. -typedef struct WrenLoadModuleResult -{ - const char* source; - WrenLoadModuleCompleteFn onComplete; - void* userData; -} WrenLoadModuleResult; - -// Loads and returns the source code for the module [name]. -typedef WrenLoadModuleResult (*WrenLoadModuleFn)(WrenVM* vm, const char* name); - -// Returns a pointer to a foreign method on [className] in [module] with -// [signature]. -typedef WrenForeignMethodFn (*WrenBindForeignMethodFn)(WrenVM* vm, - const char* module, const char* className, bool isStatic, - const char* signature); - -// Displays a string of text to the user. -typedef void (*WrenWriteFn)(WrenVM* vm, const char* text); - -typedef enum -{ - // A syntax or resolution error detected at compile time. - WREN_ERROR_COMPILE, - - // The error message for a runtime error. - WREN_ERROR_RUNTIME, - - // One entry of a runtime error's stack trace. - WREN_ERROR_STACK_TRACE -} WrenErrorType; - -// Reports an error to the user. -// -// An error detected during compile time is reported by calling this once with -// [type] `WREN_ERROR_COMPILE`, the resolved name of the [module] and [line] -// where the error occurs, and the compiler's error [message]. -// -// A runtime error is reported by calling this once with [type] -// `WREN_ERROR_RUNTIME`, no [module] or [line], and the runtime error's -// [message]. After that, a series of [type] `WREN_ERROR_STACK_TRACE` calls are -// made for each line in the stack trace. Each of those has the resolved -// [module] and [line] where the method or function is defined and [message] is -// the name of the method or function. -typedef void (*WrenErrorFn)( - WrenVM* vm, WrenErrorType type, const char* module, int line, - const char* message); - -typedef struct -{ - // The callback invoked when the foreign object is created. - // - // This must be provided. Inside the body of this, it must call - // [wrenSetSlotNewForeign()] exactly once. - WrenForeignMethodFn allocate; - - // The callback invoked when the garbage collector is about to collect a - // foreign object's memory. - // - // This may be `NULL` if the foreign class does not need to finalize. - WrenFinalizerFn finalize; -} WrenForeignClassMethods; - -// Returns a pair of pointers to the foreign methods used to allocate and -// finalize the data for instances of [className] in resolved [module]. -typedef WrenForeignClassMethods (*WrenBindForeignClassFn)( - WrenVM* vm, const char* module, const char* className); - -typedef struct -{ - // The callback Wren will use to allocate, reallocate, and deallocate memory. - // - // If `NULL`, defaults to a built-in function that uses `realloc` and `free`. - WrenReallocateFn reallocateFn; - - // The callback Wren uses to resolve a module name. - // - // Some host applications may wish to support "relative" imports, where the - // meaning of an import string depends on the module that contains it. To - // support that without baking any policy into Wren itself, the VM gives the - // host a chance to resolve an import string. - // - // Before an import is loaded, it calls this, passing in the name of the - // module that contains the import and the import string. The host app can - // look at both of those and produce a new "canonical" string that uniquely - // identifies the module. This string is then used as the name of the module - // going forward. It is what is passed to [loadModuleFn], how duplicate - // imports of the same module are detected, and how the module is reported in - // stack traces. - // - // If you leave this function NULL, then the original import string is - // treated as the resolved string. - // - // If an import cannot be resolved by the embedder, it should return NULL and - // Wren will report that as a runtime error. - // - // Wren will take ownership of the string you return and free it for you, so - // it should be allocated using the same allocation function you provide - // above. - WrenResolveModuleFn resolveModuleFn; - - // The callback Wren uses to load a module. - // - // Since Wren does not talk directly to the file system, it relies on the - // embedder to physically locate and read the source code for a module. The - // first time an import appears, Wren will call this and pass in the name of - // the module being imported. The method will return a result, which contains - // the source code for that module. Memory for the source is owned by the - // host application, and can be freed using the onComplete callback. - // - // This will only be called once for any given module name. Wren caches the - // result internally so subsequent imports of the same module will use the - // previous source and not call this. - // - // If a module with the given name could not be found by the embedder, it - // should return NULL and Wren will report that as a runtime error. - WrenLoadModuleFn loadModuleFn; - - // The callback Wren uses to find a foreign method and bind it to a class. - // - // When a foreign method is declared in a class, this will be called with the - // foreign method's module, class, and signature when the class body is - // executed. It should return a pointer to the foreign function that will be - // bound to that method. - // - // If the foreign function could not be found, this should return NULL and - // Wren will report it as runtime error. - WrenBindForeignMethodFn bindForeignMethodFn; - - // The callback Wren uses to find a foreign class and get its foreign methods. - // - // When a foreign class is declared, this will be called with the class's - // module and name when the class body is executed. It should return the - // foreign functions uses to allocate and (optionally) finalize the bytes - // stored in the foreign object when an instance is created. - WrenBindForeignClassFn bindForeignClassFn; - - // The callback Wren uses to display text when `System.print()` or the other - // related functions are called. - // - // If this is `NULL`, Wren discards any printed text. - WrenWriteFn writeFn; - - // The callback Wren uses to report errors. - // - // When an error occurs, this will be called with the module name, line - // number, and an error message. If this is `NULL`, Wren doesn't report any - // errors. - WrenErrorFn errorFn; - - // The number of bytes Wren will allocate before triggering the first garbage - // collection. - // - // If zero, defaults to 10MB. - size_t initialHeapSize; - - // After a collection occurs, the threshold for the next collection is - // determined based on the number of bytes remaining in use. This allows Wren - // to shrink its memory usage automatically after reclaiming a large amount - // of memory. - // - // This can be used to ensure that the heap does not get too small, which can - // in turn lead to a large number of collections afterwards as the heap grows - // back to a usable size. - // - // If zero, defaults to 1MB. - size_t minHeapSize; - - // Wren will resize the heap automatically as the number of bytes - // remaining in use after a collection changes. This number determines the - // amount of additional memory Wren will use after a collection, as a - // percentage of the current heap size. - // - // For example, say that this is 50. After a garbage collection, when there - // are 400 bytes of memory still in use, the next collection will be triggered - // after a total of 600 bytes are allocated (including the 400 already in - // use.) - // - // Setting this to a smaller number wastes less memory, but triggers more - // frequent garbage collections. - // - // If zero, defaults to 50. - int heapGrowthPercent; - - // User-defined data associated with the VM. - void* userData; - -} WrenConfiguration; - -typedef enum -{ - WREN_RESULT_SUCCESS, - WREN_RESULT_COMPILE_ERROR, - WREN_RESULT_RUNTIME_ERROR -} WrenInterpretResult; - -// The type of an object stored in a slot. -// -// This is not necessarily the object's *class*, but instead its low level -// representation type. -typedef enum -{ - WREN_TYPE_BOOL, - WREN_TYPE_NUM, - WREN_TYPE_FOREIGN, - WREN_TYPE_LIST, - WREN_TYPE_MAP, - WREN_TYPE_NULL, - WREN_TYPE_STRING, - - // The object is of a type that isn't accessible by the C API. - WREN_TYPE_UNKNOWN -} WrenType; - -// Get the current wren version number. -// -// Can be used to range checks over versions. -WREN_API int wrenGetVersionNumber(); - -// Initializes [configuration] with all of its default values. -// -// Call this before setting the particular fields you care about. -WREN_API void wrenInitConfiguration(WrenConfiguration* configuration); - -// Creates a new Wren virtual machine using the given [configuration]. Wren -// will copy the configuration data, so the argument passed to this can be -// freed after calling this. If [configuration] is `NULL`, uses a default -// configuration. -WREN_API WrenVM* wrenNewVM(WrenConfiguration* configuration); - -// Disposes of all resources is use by [vm], which was previously created by a -// call to [wrenNewVM]. -WREN_API void wrenFreeVM(WrenVM* vm); - -// Immediately run the garbage collector to free unused memory. -WREN_API void wrenCollectGarbage(WrenVM* vm); - -// Runs [source], a string of Wren source code in a new fiber in [vm] in the -// context of resolved [module]. -WREN_API WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module, - const char* source); - -// Creates a handle that can be used to invoke a method with [signature] on -// using a receiver and arguments that are set up on the stack. -// -// This handle can be used repeatedly to directly invoke that method from C -// code using [wrenCall]. -// -// When you are done with this handle, it must be released using -// [wrenReleaseHandle]. -WREN_API WrenHandle* wrenMakeCallHandle(WrenVM* vm, const char* signature); - -// Calls [method], using the receiver and arguments previously set up on the -// stack. -// -// [method] must have been created by a call to [wrenMakeCallHandle]. The -// arguments to the method must be already on the stack. The receiver should be -// in slot 0 with the remaining arguments following it, in order. It is an -// error if the number of arguments provided does not match the method's -// signature. -// -// After this returns, you can access the return value from slot 0 on the stack. -WREN_API WrenInterpretResult wrenCall(WrenVM* vm, WrenHandle* method); - -// Releases the reference stored in [handle]. After calling this, [handle] can -// no longer be used. -WREN_API void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle); - -// The following functions are intended to be called from foreign methods or -// finalizers. The interface Wren provides to a foreign method is like a -// register machine: you are given a numbered array of slots that values can be -// read from and written to. Values always live in a slot (unless explicitly -// captured using wrenGetSlotHandle(), which ensures the garbage collector can -// find them. -// -// When your foreign function is called, you are given one slot for the receiver -// and each argument to the method. The receiver is in slot 0 and the arguments -// are in increasingly numbered slots after that. You are free to read and -// write to those slots as you want. If you want more slots to use as scratch -// space, you can call wrenEnsureSlots() to add more. -// -// When your function returns, every slot except slot zero is discarded and the -// value in slot zero is used as the return value of the method. If you don't -// store a return value in that slot yourself, it will retain its previous -// value, the receiver. -// -// While Wren is dynamically typed, C is not. This means the C interface has to -// support the various types of primitive values a Wren variable can hold: bool, -// double, string, etc. If we supported this for every operation in the C API, -// there would be a combinatorial explosion of functions, like "get a -// double-valued element from a list", "insert a string key and double value -// into a map", etc. -// -// To avoid that, the only way to convert to and from a raw C value is by going -// into and out of a slot. All other functions work with values already in a -// slot. So, to add an element to a list, you put the list in one slot, and the -// element in another. Then there is a single API function wrenInsertInList() -// that takes the element out of that slot and puts it into the list. -// -// The goal of this API is to be easy to use while not compromising performance. -// The latter means it does not do type or bounds checking at runtime except -// using assertions which are generally removed from release builds. C is an -// unsafe language, so it's up to you to be careful to use it correctly. In -// return, you get a very fast FFI. - -// Returns the number of slots available to the current foreign method. -WREN_API int wrenGetSlotCount(WrenVM* vm); - -// Ensures that the foreign method stack has at least [numSlots] available for -// use, growing the stack if needed. -// -// Does not shrink the stack if it has more than enough slots. -// -// It is an error to call this from a finalizer. -WREN_API void wrenEnsureSlots(WrenVM* vm, int numSlots); - -// Gets the type of the object in [slot]. -WREN_API WrenType wrenGetSlotType(WrenVM* vm, int slot); - -// Reads a boolean value from [slot]. -// -// It is an error to call this if the slot does not contain a boolean value. -WREN_API bool wrenGetSlotBool(WrenVM* vm, int slot); - -// Reads a byte array from [slot]. -// -// The memory for the returned string is owned by Wren. You can inspect it -// while in your foreign method, but cannot keep a pointer to it after the -// function returns, since the garbage collector may reclaim it. -// -// Returns a pointer to the first byte of the array and fill [length] with the -// number of bytes in the array. -// -// It is an error to call this if the slot does not contain a string. -WREN_API const char* wrenGetSlotBytes(WrenVM* vm, int slot, int* length); - -// Reads a number from [slot]. -// -// It is an error to call this if the slot does not contain a number. -WREN_API double wrenGetSlotDouble(WrenVM* vm, int slot); - -// Reads a foreign object from [slot] and returns a pointer to the foreign data -// stored with it. -// -// It is an error to call this if the slot does not contain an instance of a -// foreign class. -WREN_API void* wrenGetSlotForeign(WrenVM* vm, int slot); - -// Reads a string from [slot]. -// -// The memory for the returned string is owned by Wren. You can inspect it -// while in your foreign method, but cannot keep a pointer to it after the -// function returns, since the garbage collector may reclaim it. -// -// It is an error to call this if the slot does not contain a string. -WREN_API const char* wrenGetSlotString(WrenVM* vm, int slot); - -// Creates a handle for the value stored in [slot]. -// -// This will prevent the object that is referred to from being garbage collected -// until the handle is released by calling [wrenReleaseHandle()]. -WREN_API WrenHandle* wrenGetSlotHandle(WrenVM* vm, int slot); - -// Stores the boolean [value] in [slot]. -WREN_API void wrenSetSlotBool(WrenVM* vm, int slot, bool value); - -// Stores the array [length] of [bytes] in [slot]. -// -// The bytes are copied to a new string within Wren's heap, so you can free -// memory used by them after this is called. -WREN_API void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length); - -// Stores the numeric [value] in [slot]. -WREN_API void wrenSetSlotDouble(WrenVM* vm, int slot, double value); - -// Creates a new instance of the foreign class stored in [classSlot] with [size] -// bytes of raw storage and places the resulting object in [slot]. -// -// This does not invoke the foreign class's constructor on the new instance. If -// you need that to happen, call the constructor from Wren, which will then -// call the allocator foreign method. In there, call this to create the object -// and then the constructor will be invoked when the allocator returns. -// -// Returns a pointer to the foreign object's data. -WREN_API void* wrenSetSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t size); - -// Stores a new empty list in [slot]. -WREN_API void wrenSetSlotNewList(WrenVM* vm, int slot); - -// Stores a new empty map in [slot]. -WREN_API void wrenSetSlotNewMap(WrenVM* vm, int slot); - -// Stores null in [slot]. -WREN_API void wrenSetSlotNull(WrenVM* vm, int slot); - -// Stores the string [text] in [slot]. -// -// The [text] is copied to a new string within Wren's heap, so you can free -// memory used by it after this is called. The length is calculated using -// [strlen()]. If the string may contain any null bytes in the middle, then you -// should use [wrenSetSlotBytes()] instead. -WREN_API void wrenSetSlotString(WrenVM* vm, int slot, const char* text); - -// Stores the value captured in [handle] in [slot]. -// -// This does not release the handle for the value. -WREN_API void wrenSetSlotHandle(WrenVM* vm, int slot, WrenHandle* handle); - -// Returns the number of elements in the list stored in [slot]. -WREN_API int wrenGetListCount(WrenVM* vm, int slot); - -// Reads element [index] from the list in [listSlot] and stores it in -// [elementSlot]. -WREN_API void wrenGetListElement(WrenVM* vm, int listSlot, int index, int elementSlot); - -// Sets the value stored at [index] in the list at [listSlot], -// to the value from [elementSlot]. -WREN_API void wrenSetListElement(WrenVM* vm, int listSlot, int index, int elementSlot); - -// Takes the value stored at [elementSlot] and inserts it into the list stored -// at [listSlot] at [index]. -// -// As in Wren, negative indexes can be used to insert from the end. To append -// an element, use `-1` for the index. -WREN_API void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot); - -// Returns the number of entries in the map stored in [slot]. -WREN_API int wrenGetMapCount(WrenVM* vm, int slot); - -// Returns true if the key in [keySlot] is found in the map placed in [mapSlot]. -WREN_API bool wrenGetMapContainsKey(WrenVM* vm, int mapSlot, int keySlot); - -// Retrieves a value with the key in [keySlot] from the map in [mapSlot] and -// stores it in [valueSlot]. -WREN_API void wrenGetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot); - -// Takes the value stored at [valueSlot] and inserts it into the map stored -// at [mapSlot] with key [keySlot]. -WREN_API void wrenSetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot); - -// Removes a value from the map in [mapSlot], with the key from [keySlot], -// and place it in [removedValueSlot]. If not found, [removedValueSlot] is -// set to null, the same behaviour as the Wren Map API. -WREN_API void wrenRemoveMapValue(WrenVM* vm, int mapSlot, int keySlot, - int removedValueSlot); - -// Looks up the top level variable with [name] in resolved [module] and stores -// it in [slot]. -WREN_API void wrenGetVariable(WrenVM* vm, const char* module, const char* name, - int slot); - -// Looks up the top level variable with [name] in resolved [module], -// returns false if not found. The module must be imported at the time, -// use wrenHasModule to ensure that before calling. -WREN_API bool wrenHasVariable(WrenVM* vm, const char* module, const char* name); - -// Returns true if [module] has been imported/resolved before, false if not. -WREN_API bool wrenHasModule(WrenVM* vm, const char* module); - -// Sets the current fiber to be aborted, and uses the value in [slot] as the -// runtime error object. -WREN_API void wrenAbortFiber(WrenVM* vm, int slot); - -// Returns the user data associated with the WrenVM. -WREN_API void* wrenGetUserData(WrenVM* vm); - -// Sets user data associated with the WrenVM. -WREN_API void wrenSetUserData(WrenVM* vm, void* userData); - -#endif - -// End of wren.h - -// Start of string_backend.c -// string_backend.c -#include "wren.h" -#include -#include -#include - -// Helper to validate that the value in a slot is a string. -static bool validateString(WrenVM* vm, int slot, const char* name) { - if (wrenGetSlotType(vm, slot) == WREN_TYPE_STRING) return true; - wrenSetSlotString(vm, 0, "Argument must be a string."); - wrenAbortFiber(vm, 0); - return false; -} - -// Implements String.endsWith(_). -void stringEndsWith(WrenVM* vm) { - if (!validateString(vm, 1, "Suffix")) return; - - int stringLength, suffixLength; - const char* string = wrenGetSlotBytes(vm, 0, &stringLength); - const char* suffix = wrenGetSlotBytes(vm, 1, &suffixLength); - - if (suffixLength > stringLength) { - wrenSetSlotBool(vm, 0, false); - return; - } - - wrenSetSlotBool(vm, 0, memcmp(string + stringLength - suffixLength, suffix, suffixLength) == 0); -} - -// Implements String.startsWith(_). -void stringStartsWith(WrenVM* vm) { - if (!validateString(vm, 1, "Prefix")) return; - - int stringLength, prefixLength; - const char* string = wrenGetSlotBytes(vm, 0, &stringLength); - const char* prefix = wrenGetSlotBytes(vm, 1, &prefixLength); - - if (prefixLength > stringLength) { - wrenSetSlotBool(vm, 0, false); - return; - } - - wrenSetSlotBool(vm, 0, memcmp(string, prefix, prefixLength) == 0); -} - -// Implements String.replace(_, _). -void stringReplace(WrenVM* vm) { - if (!validateString(vm, 1, "From")) return; - if (!validateString(vm, 2, "To")) return; - - int haystackLen, fromLen, toLen; - const char* haystack = wrenGetSlotBytes(vm, 0, &haystackLen); - const char* from = wrenGetSlotBytes(vm, 1, &fromLen); - const char* to = wrenGetSlotBytes(vm, 2, &toLen); - - if (fromLen == 0) { - wrenSetSlotString(vm, 0, haystack); // Nothing to replace. - return; - } - - // Allocate a buffer for the result. This is a rough estimate. - // A more robust implementation would calculate the exact size or reallocate. - size_t resultCapacity = haystackLen * (toLen > fromLen ? toLen / fromLen + 1 : 1) + 1; - char* result = (char*)malloc(resultCapacity); - if (!result) { - // Handle allocation failure - wrenSetSlotString(vm, 0, "Memory allocation failed."); - wrenAbortFiber(vm, 0); - return; - } - - - char* dest = result; - const char* p = haystack; - const char* end = haystack + haystackLen; - - while (p < end) { - const char* found = strstr(p, from); - if (found) { - size_t len = found - p; - memcpy(dest, p, len); - dest += len; - memcpy(dest, to, toLen); - dest += toLen; - p = found + fromLen; - } else { - size_t len = end - p; - memcpy(dest, p, len); - dest += len; - p = end; - } - } - *dest = '\0'; - - wrenSetSlotString(vm, 0, result); - free(result); -} - -// Implements String.split(_). -void stringSplit(WrenVM* vm) { - if (!validateString(vm, 1, "Delimiter")) return; - - int haystackLen, delimLen; - const char* haystack = wrenGetSlotBytes(vm, 0, &haystackLen); - const char* delim = wrenGetSlotBytes(vm, 1, &delimLen); - - if (delimLen == 0) { - wrenSetSlotString(vm, 0, "Delimiter cannot be empty."); - wrenAbortFiber(vm, 0); - return; - } - - wrenSetSlotNewList(vm, 0); // Create the list to return. - - const char* p = haystack; - const char* end = haystack + haystackLen; - while (p < end) { - const char* found = strstr(p, delim); - if (found) { - wrenSetSlotBytes(vm, 1, p, found - p); - wrenInsertInList(vm, 0, -1, 1); - p = found + delimLen; - } else { - wrenSetSlotBytes(vm, 1, p, end - p); - wrenInsertInList(vm, 0, -1, 1); - p = end; - } - } - - // If the string ends with the delimiter, add an empty string. - if (haystackLen > 0 && (haystackLen >= delimLen) && - strcmp(haystack + haystackLen - delimLen, delim) == 0) { - wrenSetSlotBytes(vm, 1, "", 0); - wrenInsertInList(vm, 0, -1, 1); - } -} - -// Binds the foreign methods for the String class. -WrenForeignMethodFn bindStringForeignMethod(WrenVM* vm, const char* module, const char* className, bool isStatic, const char* signature) { - if (strcmp(module, "main") != 0 && strcmp(module, "core") != 0) return NULL; - if (strcmp(className, "String") != 0 || isStatic) return NULL; - - if (strcmp(signature, "endsWith(_)") == 0) return stringEndsWith; - if (strcmp(signature, "startsWith(_)") == 0) return stringStartsWith; - if (strcmp(signature, "replace(_,_)") == 0) return stringReplace; - if (strcmp(signature, "split(_)") == 0) return stringSplit; - - return NULL; -} - - -// End of string_backend.c - -// Start of async_http.c -#include "httplib.h" -#include "wren.h" - -// A struct to hold the context for an asynchronous HTTP request -struct RequestContext { - std::string url; - WrenHandle* callback; - WrenVM* vm; - std::string response; - bool error; -}; - -// A class to manage asynchronous HTTP requests -class AsyncHttp { -public: - AsyncHttp(WrenVM* vm) : vm_(vm), running_(true) { - // Create a pool of worker threads - for (int i = 0; i < 4; ++i) { - threads_.emplace_back([this] { - while (running_) { - RequestContext* context = requestQueue_.pop(); - if (!running_) break; - - httplib::Client cli("http://example.com"); - if (auto res = cli.Get(context->url.c_str())) { - context->response = res->body; - context->error = false; - } else { - context->response = "Error: " + to_string(res.error()); - context->error = true; - } - - completionQueue_.push(context); - } - }); - } - } - - ~AsyncHttp() { - running_ = false; - // Add dummy requests to unblock worker threads - for (size_t i = 0; i < threads_.size(); ++i) { - requestQueue_.push(nullptr); - } - for (auto& thread : threads_) { - thread.join(); - } - } - - void request(const std::string& url, WrenHandle* callback) { - RequestContext* context = new RequestContext{url, callback, vm_}; - requestQueue_.push(context); - } - - void processCompletions() { - while (!completionQueue_.empty()) { - RequestContext* context = completionQueue_.pop(); - - // Create a handle for the callback function - WrenHandle* callHandle = wrenMakeCallHandle(vm_, "call(_)"); - - wrenEnsureSlots(vm_, 2); - wrenSetSlotHandle(vm_, 0, context->callback); - wrenSetSlotString(vm_, 1, context->response.c_str()); - wrenCall(vm_, callHandle); - - wrenReleaseHandle(vm_, callHandle); - wrenReleaseHandle(vm_, context->callback); - delete context; - } - } - -private: - WrenVM* vm_; - bool running_; - std::vector threads_; - ThreadSafeQueue requestQueue_; - ThreadSafeQueue completionQueue_; -}; - -// End of async_http.c - -// Start of io_backend.c -#include "wren.h" -#include -#include -#include - -#ifdef _WIN32 -#include -#else -#include -#endif - -// Helper to validate that the value in a slot is a string. -static bool io_validate_string(WrenVM* vm, int slot, const char* name) { - if (wrenGetSlotType(vm, slot) == WREN_TYPE_STRING) return true; - // The error is placed in slot 0, which becomes the return value. - wrenSetSlotString(vm, 0, "Argument must be a string."); - wrenAbortFiber(vm, 0); - return false; -} - -// --- File Class Foreign Methods --- - -void fileExists(WrenVM* vm) { - if (!io_validate_string(vm, 1, "path")) return; - const char* path = wrenGetSlotString(vm, 1); - -#ifdef _WIN32 - DWORD attrib = GetFileAttributes(path); - wrenSetSlotBool(vm, 0, (attrib != INVALID_FILE_ATTRIBUTES && !(attrib & FILE_ATTRIBUTE_DIRECTORY))); -#else - struct stat buffer; - wrenSetSlotBool(vm, 0, (stat(path, &buffer) == 0)); -#endif -} - -void fileDelete(WrenVM* vm) { - if (!io_validate_string(vm, 1, "path")) return; - const char* path = wrenGetSlotString(vm, 1); - - if (remove(path) == 0) { - wrenSetSlotBool(vm, 0, true); - } else { - wrenSetSlotBool(vm, 0, false); - } -} - -void fileRead(WrenVM* vm) { - if (!io_validate_string(vm, 1, "path")) return; - const char* path = wrenGetSlotString(vm, 1); - - FILE* file = fopen(path, "rb"); - if (file == NULL) { - wrenSetSlotNull(vm, 0); - return; - } - - fseek(file, 0L, SEEK_END); - size_t fileSize = ftell(file); - rewind(file); - - char* buffer = (char*)malloc(fileSize + 1); - if (buffer == NULL) { - fclose(file); - wrenSetSlotString(vm, 0, "Could not allocate memory to read file."); - wrenAbortFiber(vm, 0); - return; - } - - size_t bytesRead = fread(buffer, sizeof(char), fileSize, file); - if (bytesRead < fileSize) { - free(buffer); - fclose(file); - wrenSetSlotString(vm, 0, "Could not read entire file."); - wrenAbortFiber(vm, 0); - return; - } - - buffer[bytesRead] = '\0'; - fclose(file); - - wrenSetSlotBytes(vm, 0, buffer, bytesRead); - free(buffer); -} - -void fileWrite(WrenVM* vm) { - if (!io_validate_string(vm, 1, "path")) return; - if (!io_validate_string(vm, 2, "contents")) return; - - const char* path = wrenGetSlotString(vm, 1); - int length; - const char* contents = wrenGetSlotBytes(vm, 2, &length); - - FILE* file = fopen(path, "wb"); - if (file == NULL) { - wrenSetSlotBool(vm, 0, false); - return; - } - - size_t bytesWritten = fwrite(contents, sizeof(char), length, file); - fclose(file); - - wrenSetSlotBool(vm, 0, bytesWritten == (size_t)length); -} - -// --- FFI Binding --- - -WrenForeignMethodFn bindIoForeignMethod(WrenVM* vm, const char* module, const char* className, bool isStatic, const char* signature) { - if (strcmp(module, "io") != 0) return NULL; - - if (strcmp(className, "File") == 0 && isStatic) { - if (strcmp(signature, "exists(_)") == 0) return fileExists; - if (strcmp(signature, "delete(_)") == 0) return fileDelete; - if (strcmp(signature, "read(_)") == 0) return fileRead; - if (strcmp(signature, "write(_,_)") == 0) return fileWrite; - } - - return NULL; -} - - -// End of io_backend.c - -// Start of wren.c -// MIT License -// -// Copyright (c) 2013-2021 Robert Nystrom and Wren Contributors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -// Begin file "wren.h" -#ifndef wren_h -#define wren_h - -#include -#include -#include - -// The Wren semantic version number components. -#define WREN_VERSION_MAJOR 0 -#define WREN_VERSION_MINOR 4 -#define WREN_VERSION_PATCH 0 - -// A human-friendly string representation of the version. -#define WREN_VERSION_STRING "0.4.0" - -// A monotonically increasing numeric representation of the version number. Use -// this if you want to do range checks over versions. -#define WREN_VERSION_NUMBER (WREN_VERSION_MAJOR * 1000000 + \ - WREN_VERSION_MINOR * 1000 + \ - WREN_VERSION_PATCH) - -#ifndef WREN_API - #if defined(_MSC_VER) && defined(WREN_API_DLLEXPORT) - #define WREN_API __declspec( dllexport ) - #else - #define WREN_API - #endif -#endif //WREN_API - -// A single virtual machine for executing Wren code. -// -// Wren has no global state, so all state stored by a running interpreter lives -// here. -typedef struct WrenVM WrenVM; - -// A handle to a Wren object. -// -// This lets code outside of the VM hold a persistent reference to an object. -// After a handle is acquired, and until it is released, this ensures the -// garbage collector will not reclaim the object it references. -typedef struct WrenHandle WrenHandle; - -// A generic allocation function that handles all explicit memory management -// used by Wren. It's used like so: -// -// - To allocate new memory, [memory] is NULL and [newSize] is the desired -// size. It should return the allocated memory or NULL on failure. -// -// - To attempt to grow an existing allocation, [memory] is the memory, and -// [newSize] is the desired size. It should return [memory] if it was able to -// grow it in place, or a new pointer if it had to move it. -// -// - To shrink memory, [memory] and [newSize] are the same as above but it will -// always return [memory]. -// -// - To free memory, [memory] will be the memory to free and [newSize] will be -// zero. It should return NULL. -typedef void* (*WrenReallocateFn)(void* memory, size_t newSize, void* userData); - -// A function callable from Wren code, but implemented in C. -typedef void (*WrenForeignMethodFn)(WrenVM* vm); - -// A finalizer function for freeing resources owned by an instance of a foreign -// class. Unlike most foreign methods, finalizers do not have access to the VM -// and should not interact with it since it's in the middle of a garbage -// collection. -typedef void (*WrenFinalizerFn)(void* data); - -// Gives the host a chance to canonicalize the imported module name, -// potentially taking into account the (previously resolved) name of the module -// that contains the import. Typically, this is used to implement relative -// imports. -typedef const char* (*WrenResolveModuleFn)(WrenVM* vm, - const char* importer, const char* name); - -// Forward declare -struct WrenLoadModuleResult; - -// Called after loadModuleFn is called for module [name]. The original returned result -// is handed back to you in this callback, so that you can free memory if appropriate. -typedef void (*WrenLoadModuleCompleteFn)(WrenVM* vm, const char* name, struct WrenLoadModuleResult result); - -// The result of a loadModuleFn call. -// [source] is the source code for the module, or NULL if the module is not found. -// [onComplete] an optional callback that will be called once Wren is done with the result. -typedef struct WrenLoadModuleResult -{ - const char* source; - WrenLoadModuleCompleteFn onComplete; - void* userData; -} WrenLoadModuleResult; - -// Loads and returns the source code for the module [name]. -typedef WrenLoadModuleResult (*WrenLoadModuleFn)(WrenVM* vm, const char* name); - -// Returns a pointer to a foreign method on [className] in [module] with -// [signature]. -typedef WrenForeignMethodFn (*WrenBindForeignMethodFn)(WrenVM* vm, - const char* module, const char* className, bool isStatic, - const char* signature); - -// Displays a string of text to the user. -typedef void (*WrenWriteFn)(WrenVM* vm, const char* text); - -typedef enum -{ - // A syntax or resolution error detected at compile time. - WREN_ERROR_COMPILE, - - // The error message for a runtime error. - WREN_ERROR_RUNTIME, - - // One entry of a runtime error's stack trace. - WREN_ERROR_STACK_TRACE -} WrenErrorType; - -// Reports an error to the user. -// -// An error detected during compile time is reported by calling this once with -// [type] `WREN_ERROR_COMPILE`, the resolved name of the [module] and [line] -// where the error occurs, and the compiler's error [message]. -// -// A runtime error is reported by calling this once with [type] -// `WREN_ERROR_RUNTIME`, no [module] or [line], and the runtime error's -// [message]. After that, a series of [type] `WREN_ERROR_STACK_TRACE` calls are -// made for each line in the stack trace. Each of those has the resolved -// [module] and [line] where the method or function is defined and [message] is -// the name of the method or function. -typedef void (*WrenErrorFn)( - WrenVM* vm, WrenErrorType type, const char* module, int line, - const char* message); - -typedef struct -{ - // The callback invoked when the foreign object is created. - // - // This must be provided. Inside the body of this, it must call - // [wrenSetSlotNewForeign()] exactly once. - WrenForeignMethodFn allocate; - - // The callback invoked when the garbage collector is about to collect a - // foreign object's memory. - // - // This may be `NULL` if the foreign class does not need to finalize. - WrenFinalizerFn finalize; -} WrenForeignClassMethods; - -// Returns a pair of pointers to the foreign methods used to allocate and -// finalize the data for instances of [className] in resolved [module]. -typedef WrenForeignClassMethods (*WrenBindForeignClassFn)( - WrenVM* vm, const char* module, const char* className); - -typedef struct -{ - // The callback Wren will use to allocate, reallocate, and deallocate memory. - // - // If `NULL`, defaults to a built-in function that uses `realloc` and `free`. - WrenReallocateFn reallocateFn; - - // The callback Wren uses to resolve a module name. - // - // Some host applications may wish to support "relative" imports, where the - // meaning of an import string depends on the module that contains it. To - // support that without baking any policy into Wren itself, the VM gives the - // host a chance to resolve an import string. - // - // Before an import is loaded, it calls this, passing in the name of the - // module that contains the import and the import string. The host app can - // look at both of those and produce a new "canonical" string that uniquely - // identifies the module. This string is then used as the name of the module - // going forward. It is what is passed to [loadModuleFn], how duplicate - // imports of the same module are detected, and how the module is reported in - // stack traces. - // - // If you leave this function NULL, then the original import string is - // treated as the resolved string. - // - // If an import cannot be resolved by the embedder, it should return NULL and - // Wren will report that as a runtime error. - // - // Wren will take ownership of the string you return and free it for you, so - // it should be allocated using the same allocation function you provide - // above. - WrenResolveModuleFn resolveModuleFn; - - // The callback Wren uses to load a module. - // - // Since Wren does not talk directly to the file system, it relies on the - // embedder to physically locate and read the source code for a module. The - // first time an import appears, Wren will call this and pass in the name of - // the module being imported. The method will return a result, which contains - // the source code for that module. Memory for the source is owned by the - // host application, and can be freed using the onComplete callback. - // - // This will only be called once for any given module name. Wren caches the - // result internally so subsequent imports of the same module will use the - // previous source and not call this. - // - // If a module with the given name could not be found by the embedder, it - // should return NULL and Wren will report that as a runtime error. - WrenLoadModuleFn loadModuleFn; - - // The callback Wren uses to find a foreign method and bind it to a class. - // - // When a foreign method is declared in a class, this will be called with the - // foreign method's module, class, and signature when the class body is - // executed. It should return a pointer to the foreign function that will be - // bound to that method. - // - // If the foreign function could not be found, this should return NULL and - // Wren will report it as runtime error. - WrenBindForeignMethodFn bindForeignMethodFn; - - // The callback Wren uses to find a foreign class and get its foreign methods. - // - // When a foreign class is declared, this will be called with the class's - // module and name when the class body is executed. It should return the - // foreign functions uses to allocate and (optionally) finalize the bytes - // stored in the foreign object when an instance is created. - WrenBindForeignClassFn bindForeignClassFn; - - // The callback Wren uses to display text when `System.print()` or the other - // related functions are called. - // - // If this is `NULL`, Wren discards any printed text. - WrenWriteFn writeFn; - - // The callback Wren uses to report errors. - // - // When an error occurs, this will be called with the module name, line - // number, and an error message. If this is `NULL`, Wren doesn't report any - // errors. - WrenErrorFn errorFn; - - // The number of bytes Wren will allocate before triggering the first garbage - // collection. - // - // If zero, defaults to 10MB. - size_t initialHeapSize; - - // After a collection occurs, the threshold for the next collection is - // determined based on the number of bytes remaining in use. This allows Wren - // to shrink its memory usage automatically after reclaiming a large amount - // of memory. - // - // This can be used to ensure that the heap does not get too small, which can - // in turn lead to a large number of collections afterwards as the heap grows - // back to a usable size. - // - // If zero, defaults to 1MB. - size_t minHeapSize; - - // Wren will resize the heap automatically as the number of bytes - // remaining in use after a collection changes. This number determines the - // amount of additional memory Wren will use after a collection, as a - // percentage of the current heap size. - // - // For example, say that this is 50. After a garbage collection, when there - // are 400 bytes of memory still in use, the next collection will be triggered - // after a total of 600 bytes are allocated (including the 400 already in - // use.) - // - // Setting this to a smaller number wastes less memory, but triggers more - // frequent garbage collections. - // - // If zero, defaults to 50. - int heapGrowthPercent; - - // User-defined data associated with the VM. - void* userData; - -} WrenConfiguration; - -typedef enum -{ - WREN_RESULT_SUCCESS, - WREN_RESULT_COMPILE_ERROR, - WREN_RESULT_RUNTIME_ERROR -} WrenInterpretResult; - -// The type of an object stored in a slot. -// -// This is not necessarily the object's *class*, but instead its low level -// representation type. -typedef enum -{ - WREN_TYPE_BOOL, - WREN_TYPE_NUM, - WREN_TYPE_FOREIGN, - WREN_TYPE_LIST, - WREN_TYPE_MAP, - WREN_TYPE_NULL, - WREN_TYPE_STRING, - - // The object is of a type that isn't accessible by the C API. - WREN_TYPE_UNKNOWN -} WrenType; - -// Get the current wren version number. -// -// Can be used to range checks over versions. -WREN_API int wrenGetVersionNumber(); - -// Initializes [configuration] with all of its default values. -// -// Call this before setting the particular fields you care about. -WREN_API void wrenInitConfiguration(WrenConfiguration* configuration); - -// Creates a new Wren virtual machine using the given [configuration]. Wren -// will copy the configuration data, so the argument passed to this can be -// freed after calling this. If [configuration] is `NULL`, uses a default -// configuration. -WREN_API WrenVM* wrenNewVM(WrenConfiguration* configuration); - -// Disposes of all resources is use by [vm], which was previously created by a -// call to [wrenNewVM]. -WREN_API void wrenFreeVM(WrenVM* vm); - -// Immediately run the garbage collector to free unused memory. -WREN_API void wrenCollectGarbage(WrenVM* vm); - -// Runs [source], a string of Wren source code in a new fiber in [vm] in the -// context of resolved [module]. -WREN_API WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module, - const char* source); - -// Creates a handle that can be used to invoke a method with [signature] on -// using a receiver and arguments that are set up on the stack. -// -// This handle can be used repeatedly to directly invoke that method from C -// code using [wrenCall]. -// -// When you are done with this handle, it must be released using -// [wrenReleaseHandle]. -WREN_API WrenHandle* wrenMakeCallHandle(WrenVM* vm, const char* signature); - -// Calls [method], using the receiver and arguments previously set up on the -// stack. -// -// [method] must have been created by a call to [wrenMakeCallHandle]. The -// arguments to the method must be already on the stack. The receiver should be -// in slot 0 with the remaining arguments following it, in order. It is an -// error if the number of arguments provided does not match the method's -// signature. -// -// After this returns, you can access the return value from slot 0 on the stack. -WREN_API WrenInterpretResult wrenCall(WrenVM* vm, WrenHandle* method); - -// Releases the reference stored in [handle]. After calling this, [handle] can -// no longer be used. -WREN_API void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle); - -// The following functions are intended to be called from foreign methods or -// finalizers. The interface Wren provides to a foreign method is like a -// register machine: you are given a numbered array of slots that values can be -// read from and written to. Values always live in a slot (unless explicitly -// captured using wrenGetSlotHandle(), which ensures the garbage collector can -// find them. -// -// When your foreign function is called, you are given one slot for the receiver -// and each argument to the method. The receiver is in slot 0 and the arguments -// are in increasingly numbered slots after that. You are free to read and -// write to those slots as you want. If you want more slots to use as scratch -// space, you can call wrenEnsureSlots() to add more. -// -// When your function returns, every slot except slot zero is discarded and the -// value in slot zero is used as the return value of the method. If you don't -// store a return value in that slot yourself, it will retain its previous -// value, the receiver. -// -// While Wren is dynamically typed, C is not. This means the C interface has to -// support the various types of primitive values a Wren variable can hold: bool, -// double, string, etc. If we supported this for every operation in the C API, -// there would be a combinatorial explosion of functions, like "get a -// double-valued element from a list", "insert a string key and double value -// into a map", etc. -// -// To avoid that, the only way to convert to and from a raw C value is by going -// into and out of a slot. All other functions work with values already in a -// slot. So, to add an element to a list, you put the list in one slot, and the -// element in another. Then there is a single API function wrenInsertInList() -// that takes the element out of that slot and puts it into the list. -// -// The goal of this API is to be easy to use while not compromising performance. -// The latter means it does not do type or bounds checking at runtime except -// using assertions which are generally removed from release builds. C is an -// unsafe language, so it's up to you to be careful to use it correctly. In -// return, you get a very fast FFI. - -// Returns the number of slots available to the current foreign method. -WREN_API int wrenGetSlotCount(WrenVM* vm); - -// Ensures that the foreign method stack has at least [numSlots] available for -// use, growing the stack if needed. -// -// Does not shrink the stack if it has more than enough slots. -// -// It is an error to call this from a finalizer. -WREN_API void wrenEnsureSlots(WrenVM* vm, int numSlots); - -// Gets the type of the object in [slot]. -WREN_API WrenType wrenGetSlotType(WrenVM* vm, int slot); - -// Reads a boolean value from [slot]. -// -// It is an error to call this if the slot does not contain a boolean value. -WREN_API bool wrenGetSlotBool(WrenVM* vm, int slot); - -// Reads a byte array from [slot]. -// -// The memory for the returned string is owned by Wren. You can inspect it -// while in your foreign method, but cannot keep a pointer to it after the -// function returns, since the garbage collector may reclaim it. -// -// Returns a pointer to the first byte of the array and fill [length] with the -// number of bytes in the array. -// -// It is an error to call this if the slot does not contain a string. -WREN_API const char* wrenGetSlotBytes(WrenVM* vm, int slot, int* length); - -// Reads a number from [slot]. -// -// It is an error to call this if the slot does not contain a number. -WREN_API double wrenGetSlotDouble(WrenVM* vm, int slot); - -// Reads a foreign object from [slot] and returns a pointer to the foreign data -// stored with it. -// -// It is an error to call this if the slot does not contain an instance of a -// foreign class. -WREN_API void* wrenGetSlotForeign(WrenVM* vm, int slot); - -// Reads a string from [slot]. -// -// The memory for the returned string is owned by Wren. You can inspect it -// while in your foreign method, but cannot keep a pointer to it after the -// function returns, since the garbage collector may reclaim it. -// -// It is an error to call this if the slot does not contain a string. -WREN_API const char* wrenGetSlotString(WrenVM* vm, int slot); - -// Creates a handle for the value stored in [slot]. -// -// This will prevent the object that is referred to from being garbage collected -// until the handle is released by calling [wrenReleaseHandle()]. -WREN_API WrenHandle* wrenGetSlotHandle(WrenVM* vm, int slot); - -// Stores the boolean [value] in [slot]. -WREN_API void wrenSetSlotBool(WrenVM* vm, int slot, bool value); - -// Stores the array [length] of [bytes] in [slot]. -// -// The bytes are copied to a new string within Wren's heap, so you can free -// memory used by them after this is called. -WREN_API void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length); - -// Stores the numeric [value] in [slot]. -WREN_API void wrenSetSlotDouble(WrenVM* vm, int slot, double value); - -// Creates a new instance of the foreign class stored in [classSlot] with [size] -// bytes of raw storage and places the resulting object in [slot]. -// -// This does not invoke the foreign class's constructor on the new instance. If -// you need that to happen, call the constructor from Wren, which will then -// call the allocator foreign method. In there, call this to create the object -// and then the constructor will be invoked when the allocator returns. -// -// Returns a pointer to the foreign object's data. -WREN_API void* wrenSetSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t size); - -// Stores a new empty list in [slot]. -WREN_API void wrenSetSlotNewList(WrenVM* vm, int slot); - -// Stores a new empty map in [slot]. -WREN_API void wrenSetSlotNewMap(WrenVM* vm, int slot); - -// Stores null in [slot]. -WREN_API void wrenSetSlotNull(WrenVM* vm, int slot); - -// Stores the string [text] in [slot]. -// -// The [text] is copied to a new string within Wren's heap, so you can free -// memory used by it after this is called. The length is calculated using -// [strlen()]. If the string may contain any null bytes in the middle, then you -// should use [wrenSetSlotBytes()] instead. -WREN_API void wrenSetSlotString(WrenVM* vm, int slot, const char* text); - -// Stores the value captured in [handle] in [slot]. -// -// This does not release the handle for the value. -WREN_API void wrenSetSlotHandle(WrenVM* vm, int slot, WrenHandle* handle); - -// Returns the number of elements in the list stored in [slot]. -WREN_API int wrenGetListCount(WrenVM* vm, int slot); - -// Reads element [index] from the list in [listSlot] and stores it in -// [elementSlot]. -WREN_API void wrenGetListElement(WrenVM* vm, int listSlot, int index, int elementSlot); - -// Sets the value stored at [index] in the list at [listSlot], -// to the value from [elementSlot]. -WREN_API void wrenSetListElement(WrenVM* vm, int listSlot, int index, int elementSlot); - -// Takes the value stored at [elementSlot] and inserts it into the list stored -// at [listSlot] at [index]. -// -// As in Wren, negative indexes can be used to insert from the end. To append -// an element, use `-1` for the index. -WREN_API void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot); - -// Returns the number of entries in the map stored in [slot]. -WREN_API int wrenGetMapCount(WrenVM* vm, int slot); - -// Returns true if the key in [keySlot] is found in the map placed in [mapSlot]. -WREN_API bool wrenGetMapContainsKey(WrenVM* vm, int mapSlot, int keySlot); - -// Retrieves a value with the key in [keySlot] from the map in [mapSlot] and -// stores it in [valueSlot]. -WREN_API void wrenGetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot); - -// Takes the value stored at [valueSlot] and inserts it into the map stored -// at [mapSlot] with key [keySlot]. -WREN_API void wrenSetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot); - -// Removes a value from the map in [mapSlot], with the key from [keySlot], -// and place it in [removedValueSlot]. If not found, [removedValueSlot] is -// set to null, the same behaviour as the Wren Map API. -WREN_API void wrenRemoveMapValue(WrenVM* vm, int mapSlot, int keySlot, - int removedValueSlot); - -// Looks up the top level variable with [name] in resolved [module] and stores -// it in [slot]. -WREN_API void wrenGetVariable(WrenVM* vm, const char* module, const char* name, - int slot); - -// Looks up the top level variable with [name] in resolved [module], -// returns false if not found. The module must be imported at the time, -// use wrenHasModule to ensure that before calling. -WREN_API bool wrenHasVariable(WrenVM* vm, const char* module, const char* name); - -// Returns true if [module] has been imported/resolved before, false if not. -WREN_API bool wrenHasModule(WrenVM* vm, const char* module); - -// Sets the current fiber to be aborted, and uses the value in [slot] as the -// runtime error object. -WREN_API void wrenAbortFiber(WrenVM* vm, int slot); - -// Returns the user data associated with the WrenVM. -WREN_API void* wrenGetUserData(WrenVM* vm); - -// Sets user data associated with the WrenVM. -WREN_API void wrenSetUserData(WrenVM* vm, void* userData); - -#endif -// End file "wren.h" -// Begin file "wren_debug.h" -#ifndef wren_debug_h -#define wren_debug_h - -// Begin file "wren_value.h" -#ifndef wren_value_h -#define wren_value_h - -#include -#include - -// Begin file "wren_common.h" -#ifndef wren_common_h -#define wren_common_h - -// This header contains macros and defines used across the entire Wren -// implementation. In particular, it contains "configuration" defines that -// control how Wren works. Some of these are only used while hacking on Wren -// itself. -// -// This header is *not* intended to be included by code outside of Wren itself. - -// Wren pervasively uses the C99 integer types (uint16_t, etc.) along with some -// of the associated limit constants (UINT32_MAX, etc.). The constants are not -// part of standard C++, so aren't included by default by C++ compilers when you -// include unless __STDC_LIMIT_MACROS is defined. -#define __STDC_LIMIT_MACROS -#include - -// These flags let you control some details of the interpreter's implementation. -// Usually they trade-off a bit of portability for speed. They default to the -// most efficient behavior. - -// If true, then Wren uses a NaN-tagged double for its core value -// representation. Otherwise, it uses a larger more conventional struct. The -// former is significantly faster and more compact. The latter is useful for -// debugging and may be more portable. -// -// Defaults to on. -#ifndef WREN_NAN_TAGGING - #define WREN_NAN_TAGGING 1 -#endif - -// If true, the VM's interpreter loop uses computed gotos. See this for more: -// http://gcc.gnu.org/onlinedocs/gcc-3.1.1/gcc/Labels-as-Values.html -// Enabling this speeds up the main dispatch loop a bit, but requires compiler -// support. -// see https://bullno1.com/blog/switched-goto for alternative -// Defaults to true on supported compilers. -#ifndef WREN_COMPUTED_GOTO - #if defined(_MSC_VER) && !defined(__clang__) - // No computed gotos in Visual Studio. - #define WREN_COMPUTED_GOTO 0 - #else - #define WREN_COMPUTED_GOTO 1 - #endif -#endif - -// The VM includes a number of optional modules. You can choose to include -// these or not. By default, they are all available. To disable one, set the -// corresponding `WREN_OPT_` define to `0`. -#ifndef WREN_OPT_META - #define WREN_OPT_META 1 -#endif - -#ifndef WREN_OPT_RANDOM - #define WREN_OPT_RANDOM 1 -#endif - -// These flags are useful for debugging and hacking on Wren itself. They are not -// intended to be used for production code. They default to off. - -// Set this to true to stress test the GC. It will perform a collection before -// every allocation. This is useful to ensure that memory is always correctly -// reachable. -#define WREN_DEBUG_GC_STRESS 0 - -// Set this to true to log memory operations as they occur. -#define WREN_DEBUG_TRACE_MEMORY 0 - -// Set this to true to log garbage collections as they occur. -#define WREN_DEBUG_TRACE_GC 0 - -// Set this to true to print out the compiled bytecode of each function. -#define WREN_DEBUG_DUMP_COMPILED_CODE 0 - -// Set this to trace each instruction as it's executed. -#define WREN_DEBUG_TRACE_INSTRUCTIONS 0 - -// The maximum number of module-level variables that may be defined at one time. -// This limitation comes from the 16 bits used for the arguments to -// `CODE_LOAD_MODULE_VAR` and `CODE_STORE_MODULE_VAR`. -#define MAX_MODULE_VARS 65536 - -// The maximum number of arguments that can be passed to a method. Note that -// this limitation is hardcoded in other places in the VM, in particular, the -// `CODE_CALL_XX` instructions assume a certain maximum number. -#define MAX_PARAMETERS 16 - -// The maximum name of a method, not including the signature. This is an -// arbitrary but enforced maximum just so we know how long the method name -// strings need to be in the parser. -#define MAX_METHOD_NAME 64 - -// The maximum length of a method signature. Signatures look like: -// -// foo // Getter. -// foo() // No-argument method. -// foo(_) // One-argument method. -// foo(_,_) // Two-argument method. -// init foo() // Constructor initializer. -// -// The maximum signature length takes into account the longest method name, the -// maximum number of parameters with separators between them, "init ", and "()". -#define MAX_METHOD_SIGNATURE (MAX_METHOD_NAME + (MAX_PARAMETERS * 2) + 6) - -// The maximum length of an identifier. The only real reason for this limitation -// is so that error messages mentioning variables can be stack allocated. -#define MAX_VARIABLE_NAME 64 - -// The maximum number of fields a class can have, including inherited fields. -// This is explicit in the bytecode since `CODE_CLASS` and `CODE_SUBCLASS` take -// a single byte for the number of fields. Note that it's 255 and not 256 -// because creating a class takes the *number* of fields, not the *highest -// field index*. -#define MAX_FIELDS 255 - -// Use the VM's allocator to allocate an object of [type]. -#define ALLOCATE(vm, type) \ - ((type*)wrenReallocate(vm, NULL, 0, sizeof(type))) - -// Use the VM's allocator to allocate an object of [mainType] containing a -// flexible array of [count] objects of [arrayType]. -#define ALLOCATE_FLEX(vm, mainType, arrayType, count) \ - ((mainType*)wrenReallocate(vm, NULL, 0, \ - sizeof(mainType) + sizeof(arrayType) * (count))) - -// Use the VM's allocator to allocate an array of [count] elements of [type]. -#define ALLOCATE_ARRAY(vm, type, count) \ - ((type*)wrenReallocate(vm, NULL, 0, sizeof(type) * (count))) - -// Use the VM's allocator to free the previously allocated memory at [pointer]. -#define DEALLOCATE(vm, pointer) wrenReallocate(vm, pointer, 0, 0) - -// The Microsoft compiler does not support the "inline" modifier when compiling -// as plain C. -#if defined( _MSC_VER ) && !defined(__cplusplus) - #define inline _inline -#endif - -// This is used to clearly mark flexible-sized arrays that appear at the end of -// some dynamically-allocated structs, known as the "struct hack". -#if __STDC_VERSION__ >= 199901L - // In C99, a flexible array member is just "[]". - #define FLEXIBLE_ARRAY -#else - // Elsewhere, use a zero-sized array. It's technically undefined behavior, - // but works reliably in most known compilers. - #define FLEXIBLE_ARRAY 0 -#endif - -// Assertions are used to validate program invariants. They indicate things the -// program expects to be true about its internal state during execution. If an -// assertion fails, there is a bug in Wren. -// -// Assertions add significant overhead, so are only enabled in debug builds. -#ifdef DEBUG - - #include - - #define ASSERT(condition, message) \ - do \ - { \ - if (!(condition)) \ - { \ - fprintf(stderr, "[%s:%d] Assert failed in %s(): %s\n", \ - __FILE__, __LINE__, __func__, message); \ - abort(); \ - } \ - } while (false) - - // Indicates that we know execution should never reach this point in the - // program. In debug mode, we assert this fact because it's a bug to get here. - // - // In release mode, we use compiler-specific built in functions to tell the - // compiler the code can't be reached. This avoids "missing return" warnings - // in some cases and also lets it perform some optimizations by assuming the - // code is never reached. - #define UNREACHABLE() \ - do \ - { \ - fprintf(stderr, "[%s:%d] This code should not be reached in %s()\n", \ - __FILE__, __LINE__, __func__); \ - abort(); \ - } while (false) - -#else - - #define ASSERT(condition, message) do { } while (false) - - // Tell the compiler that this part of the code will never be reached. - #if defined( _MSC_VER ) - #define UNREACHABLE() __assume(0) - #elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) - #define UNREACHABLE() __builtin_unreachable() - #else - #define UNREACHABLE() - #endif - -#endif - -#endif -// End file "wren_common.h" -// Begin file "wren_math.h" -#ifndef wren_math_h -#define wren_math_h - -#include -#include - -// A union to let us reinterpret a double as raw bits and back. -typedef union -{ - uint64_t bits64; - uint32_t bits32[2]; - double num; -} WrenDoubleBits; - -#define WREN_DOUBLE_QNAN_POS_MIN_BITS (UINT64_C(0x7FF8000000000000)) -#define WREN_DOUBLE_QNAN_POS_MAX_BITS (UINT64_C(0x7FFFFFFFFFFFFFFF)) - -#define WREN_DOUBLE_NAN (wrenDoubleFromBits(WREN_DOUBLE_QNAN_POS_MIN_BITS)) - -static inline double wrenDoubleFromBits(uint64_t bits) -{ - WrenDoubleBits data; - data.bits64 = bits; - return data.num; -} - -static inline uint64_t wrenDoubleToBits(double num) -{ - WrenDoubleBits data; - data.num = num; - return data.bits64; -} - -#endif -// End file "wren_math.h" -// Begin file "wren_utils.h" -#ifndef wren_utils_h -#define wren_utils_h - - -// Reusable data structures and other utility functions. - -// Forward declare this here to break a cycle between wren_utils.h and -// wren_value.h. -typedef struct sObjString ObjString; - -// We need buffers of a few different types. To avoid lots of casting between -// void* and back, we'll use the preprocessor as a poor man's generics and let -// it generate a few type-specific ones. -#define DECLARE_BUFFER(name, type) \ - typedef struct \ - { \ - type* data; \ - int count; \ - int capacity; \ - } name##Buffer; \ - void wren##name##BufferInit(name##Buffer* buffer); \ - void wren##name##BufferClear(WrenVM* vm, name##Buffer* buffer); \ - void wren##name##BufferFill(WrenVM* vm, name##Buffer* buffer, type data, \ - int count); \ - void wren##name##BufferWrite(WrenVM* vm, name##Buffer* buffer, type data) - -// This should be used once for each type instantiation, somewhere in a .c file. -#define DEFINE_BUFFER(name, type) \ - void wren##name##BufferInit(name##Buffer* buffer) \ - { \ - buffer->data = NULL; \ - buffer->capacity = 0; \ - buffer->count = 0; \ - } \ - \ - void wren##name##BufferClear(WrenVM* vm, name##Buffer* buffer) \ - { \ - wrenReallocate(vm, buffer->data, 0, 0); \ - wren##name##BufferInit(buffer); \ - } \ - \ - void wren##name##BufferFill(WrenVM* vm, name##Buffer* buffer, type data, \ - int count) \ - { \ - if (buffer->capacity < buffer->count + count) \ - { \ - int capacity = wrenPowerOf2Ceil(buffer->count + count); \ - buffer->data = (type*)wrenReallocate(vm, buffer->data, \ - buffer->capacity * sizeof(type), capacity * sizeof(type)); \ - buffer->capacity = capacity; \ - } \ - \ - for (int i = 0; i < count; i++) \ - { \ - buffer->data[buffer->count++] = data; \ - } \ - } \ - \ - void wren##name##BufferWrite(WrenVM* vm, name##Buffer* buffer, type data) \ - { \ - wren##name##BufferFill(vm, buffer, data, 1); \ - } - -DECLARE_BUFFER(Byte, uint8_t); -DECLARE_BUFFER(Int, int); -DECLARE_BUFFER(String, ObjString*); - -// TODO: Change this to use a map. -typedef StringBuffer SymbolTable; - -// Initializes the symbol table. -void wrenSymbolTableInit(SymbolTable* symbols); - -// Frees all dynamically allocated memory used by the symbol table, but not the -// SymbolTable itself. -void wrenSymbolTableClear(WrenVM* vm, SymbolTable* symbols); - -// Adds name to the symbol table. Returns the index of it in the table. -int wrenSymbolTableAdd(WrenVM* vm, SymbolTable* symbols, - const char* name, size_t length); - -// Adds name to the symbol table. Returns the index of it in the table. Will -// use an existing symbol if already present. -int wrenSymbolTableEnsure(WrenVM* vm, SymbolTable* symbols, - const char* name, size_t length); - -// Looks up name in the symbol table. Returns its index if found or -1 if not. -int wrenSymbolTableFind(const SymbolTable* symbols, - const char* name, size_t length); - -void wrenBlackenSymbolTable(WrenVM* vm, SymbolTable* symbolTable); - -// Returns the number of bytes needed to encode [value] in UTF-8. -// -// Returns 0 if [value] is too large to encode. -int wrenUtf8EncodeNumBytes(int value); - -// Encodes value as a series of bytes in [bytes], which is assumed to be large -// enough to hold the encoded result. -// -// Returns the number of written bytes. -int wrenUtf8Encode(int value, uint8_t* bytes); - -// Decodes the UTF-8 sequence starting at [bytes] (which has max [length]), -// returning the code point. -// -// Returns -1 if the bytes are not a valid UTF-8 sequence. -int wrenUtf8Decode(const uint8_t* bytes, uint32_t length); - -// Returns the number of bytes in the UTF-8 sequence starting with [byte]. -// -// If the character at that index is not the beginning of a UTF-8 sequence, -// returns 0. -int wrenUtf8DecodeNumBytes(uint8_t byte); - -// Returns the smallest power of two that is equal to or greater than [n]. -int wrenPowerOf2Ceil(int n); - -// Validates that [value] is within `[0, count)`. Also allows -// negative indices which map backwards from the end. Returns the valid positive -// index value. If invalid, returns `UINT32_MAX`. -uint32_t wrenValidateIndex(uint32_t count, int64_t value); - -#endif -// End file "wren_utils.h" - -// This defines the built-in types and their core representations in memory. -// Since Wren is dynamically typed, any variable can hold a value of any type, -// and the type can change at runtime. Implementing this efficiently is -// critical for performance. -// -// The main type exposed by this is [Value]. A C variable of that type is a -// storage location that can hold any Wren value. The stack, module variables, -// and instance fields are all implemented in C as variables of type Value. -// -// The built-in types for booleans, numbers, and null are unboxed: their value -// is stored directly in the Value, and copying a Value copies the value. Other -// types--classes, instances of classes, functions, lists, and strings--are all -// reference types. They are stored on the heap and the Value just stores a -// pointer to it. Copying the Value copies a reference to the same object. The -// Wren implementation calls these "Obj", or objects, though to a user, all -// values are objects. -// -// There is also a special singleton value "undefined". It is used internally -// but never appears as a real value to a user. It has two uses: -// -// - It is used to identify module variables that have been implicitly declared -// by use in a forward reference but not yet explicitly declared. These only -// exist during compilation and do not appear at runtime. -// -// - It is used to represent unused map entries in an ObjMap. -// -// There are two supported Value representations. The main one uses a technique -// called "NaN tagging" (explained in detail below) to store a number, any of -// the value types, or a pointer, all inside one double-precision floating -// point number. A larger, slower, Value type that uses a struct to store these -// is also supported, and is useful for debugging the VM. -// -// The representation is controlled by the `WREN_NAN_TAGGING` define. If that's -// defined, Nan tagging is used. - -// These macros cast a Value to one of the specific object types. These do *not* -// perform any validation, so must only be used after the Value has been -// ensured to be the right type. -#define AS_CLASS(value) ((ObjClass*)AS_OBJ(value)) // ObjClass* -#define AS_CLOSURE(value) ((ObjClosure*)AS_OBJ(value)) // ObjClosure* -#define AS_FIBER(v) ((ObjFiber*)AS_OBJ(v)) // ObjFiber* -#define AS_FN(value) ((ObjFn*)AS_OBJ(value)) // ObjFn* -#define AS_FOREIGN(v) ((ObjForeign*)AS_OBJ(v)) // ObjForeign* -#define AS_INSTANCE(value) ((ObjInstance*)AS_OBJ(value)) // ObjInstance* -#define AS_LIST(value) ((ObjList*)AS_OBJ(value)) // ObjList* -#define AS_MAP(value) ((ObjMap*)AS_OBJ(value)) // ObjMap* -#define AS_MODULE(value) ((ObjModule*)AS_OBJ(value)) // ObjModule* -#define AS_NUM(value) (wrenValueToNum(value)) // double -#define AS_RANGE(v) ((ObjRange*)AS_OBJ(v)) // ObjRange* -#define AS_STRING(v) ((ObjString*)AS_OBJ(v)) // ObjString* -#define AS_CSTRING(v) (AS_STRING(v)->value) // const char* - -// These macros promote a primitive C value to a full Wren Value. There are -// more defined below that are specific to the Nan tagged or other -// representation. -#define BOOL_VAL(boolean) ((boolean) ? TRUE_VAL : FALSE_VAL) // boolean -#define NUM_VAL(num) (wrenNumToValue(num)) // double -#define OBJ_VAL(obj) (wrenObjectToValue((Obj*)(obj))) // Any Obj___* - -// These perform type tests on a Value, returning `true` if the Value is of the -// given type. -#define IS_BOOL(value) (wrenIsBool(value)) // Bool -#define IS_CLASS(value) (wrenIsObjType(value, OBJ_CLASS)) // ObjClass -#define IS_CLOSURE(value) (wrenIsObjType(value, OBJ_CLOSURE)) // ObjClosure -#define IS_FIBER(value) (wrenIsObjType(value, OBJ_FIBER)) // ObjFiber -#define IS_FN(value) (wrenIsObjType(value, OBJ_FN)) // ObjFn -#define IS_FOREIGN(value) (wrenIsObjType(value, OBJ_FOREIGN)) // ObjForeign -#define IS_INSTANCE(value) (wrenIsObjType(value, OBJ_INSTANCE)) // ObjInstance -#define IS_LIST(value) (wrenIsObjType(value, OBJ_LIST)) // ObjList -#define IS_MAP(value) (wrenIsObjType(value, OBJ_MAP)) // ObjMap -#define IS_RANGE(value) (wrenIsObjType(value, OBJ_RANGE)) // ObjRange -#define IS_STRING(value) (wrenIsObjType(value, OBJ_STRING)) // ObjString - -// Creates a new string object from [text], which should be a bare C string -// literal. This determines the length of the string automatically at compile -// time based on the size of the character array (-1 for the terminating '\0'). -#define CONST_STRING(vm, text) wrenNewStringLength((vm), (text), sizeof(text) - 1) - -// Identifies which specific type a heap-allocated object is. -typedef enum { - OBJ_CLASS, - OBJ_CLOSURE, - OBJ_FIBER, - OBJ_FN, - OBJ_FOREIGN, - OBJ_INSTANCE, - OBJ_LIST, - OBJ_MAP, - OBJ_MODULE, - OBJ_RANGE, - OBJ_STRING, - OBJ_UPVALUE -} ObjType; - -typedef struct sObjClass ObjClass; - -// Base struct for all heap-allocated objects. -typedef struct sObj Obj; -struct sObj -{ - ObjType type; - bool isDark; - - // The object's class. - ObjClass* classObj; - - // The next object in the linked list of all currently allocated objects. - struct sObj* next; -}; - -#if WREN_NAN_TAGGING - -typedef uint64_t Value; - -#else - -typedef enum -{ - VAL_FALSE, - VAL_NULL, - VAL_NUM, - VAL_TRUE, - VAL_UNDEFINED, - VAL_OBJ -} ValueType; - -typedef struct -{ - ValueType type; - union - { - double num; - Obj* obj; - } as; -} Value; - -#endif - -DECLARE_BUFFER(Value, Value); - -// A heap-allocated string object. -struct sObjString -{ - Obj obj; - - // Number of bytes in the string, not including the null terminator. - uint32_t length; - - // The hash value of the string's contents. - uint32_t hash; - - // Inline array of the string's bytes followed by a null terminator. - char value[FLEXIBLE_ARRAY]; -}; - -// The dynamically allocated data structure for a variable that has been used -// by a closure. Whenever a function accesses a variable declared in an -// enclosing function, it will get to it through this. -// -// An upvalue can be either "closed" or "open". An open upvalue points directly -// to a [Value] that is still stored on the fiber's stack because the local -// variable is still in scope in the function where it's declared. -// -// When that local variable goes out of scope, the upvalue pointing to it will -// be closed. When that happens, the value gets copied off the stack into the -// upvalue itself. That way, it can have a longer lifetime than the stack -// variable. -typedef struct sObjUpvalue -{ - // The object header. Note that upvalues have this because they are garbage - // collected, but they are not first class Wren objects. - Obj obj; - - // Pointer to the variable this upvalue is referencing. - Value* value; - - // If the upvalue is closed (i.e. the local variable it was pointing to has - // been popped off the stack) then the closed-over value will be hoisted out - // of the stack into here. [value] will then be changed to point to this. - Value closed; - - // Open upvalues are stored in a linked list by the fiber. This points to the - // next upvalue in that list. - struct sObjUpvalue* next; -} ObjUpvalue; - -// The type of a primitive function. -// -// Primitives are similar to foreign functions, but have more direct access to -// VM internals. It is passed the arguments in [args]. If it returns a value, -// it places it in `args[0]` and returns `true`. If it causes a runtime error -// or modifies the running fiber, it returns `false`. -typedef bool (*Primitive)(WrenVM* vm, Value* args); - -// TODO: See if it's actually a perf improvement to have this in a separate -// struct instead of in ObjFn. -// Stores debugging information for a function used for things like stack -// traces. -typedef struct -{ - // The name of the function. Heap allocated and owned by the FnDebug. - char* name; - - // An array of line numbers. There is one element in this array for each - // bytecode in the function's bytecode array. The value of that element is - // the line in the source code that generated that instruction. - IntBuffer sourceLines; -} FnDebug; - -// A loaded module and the top-level variables it defines. -// -// While this is an Obj and is managed by the GC, it never appears as a -// first-class object in Wren. -typedef struct -{ - Obj obj; - - // The currently defined top-level variables. - ValueBuffer variables; - - // Symbol table for the names of all module variables. Indexes here directly - // correspond to entries in [variables]. - SymbolTable variableNames; - - // The name of the module. - ObjString* name; -} ObjModule; - -// A function object. It wraps and owns the bytecode and other debug information -// for a callable chunk of code. -// -// Function objects are not passed around and invoked directly. Instead, they -// are always referenced by an [ObjClosure] which is the real first-class -// representation of a function. This isn't strictly necessary if they function -// has no upvalues, but lets the rest of the VM assume all called objects will -// be closures. -typedef struct -{ - Obj obj; - - ByteBuffer code; - ValueBuffer constants; - - // The module where this function was defined. - ObjModule* module; - - // The maximum number of stack slots this function may use. - int maxSlots; - - // The number of upvalues this function closes over. - int numUpvalues; - - // The number of parameters this function expects. Used to ensure that .call - // handles a mismatch between number of parameters and arguments. This will - // only be set for fns, and not ObjFns that represent methods or scripts. - int arity; - FnDebug* debug; -} ObjFn; - -// An instance of a first-class function and the environment it has closed over. -// Unlike [ObjFn], this has captured the upvalues that the function accesses. -typedef struct -{ - Obj obj; - - // The function that this closure is an instance of. - ObjFn* fn; - - // The upvalues this function has closed over. - ObjUpvalue* upvalues[FLEXIBLE_ARRAY]; -} ObjClosure; - -typedef struct -{ - // Pointer to the current (really next-to-be-executed) instruction in the - // function's bytecode. - uint8_t* ip; - - // The closure being executed. - ObjClosure* closure; - - // Pointer to the first stack slot used by this call frame. This will contain - // the receiver, followed by the function's parameters, then local variables - // and temporaries. - Value* stackStart; -} CallFrame; - -// Tracks how this fiber has been invoked, aside from the ways that can be -// detected from the state of other fields in the fiber. -typedef enum -{ - // The fiber is being run from another fiber using a call to `try()`. - FIBER_TRY, - - // The fiber was directly invoked by `runInterpreter()`. This means it's the - // initial fiber used by a call to `wrenCall()` or `wrenInterpret()`. - FIBER_ROOT, - - // The fiber is invoked some other way. If [caller] is `NULL` then the fiber - // was invoked using `call()`. If [numFrames] is zero, then the fiber has - // finished running and is done. If [numFrames] is one and that frame's `ip` - // points to the first byte of code, the fiber has not been started yet. - FIBER_OTHER, -} FiberState; - -typedef struct sObjFiber -{ - Obj obj; - - // The stack of value slots. This is used for holding local variables and - // temporaries while the fiber is executing. It is heap-allocated and grown - // as needed. - Value* stack; - - // A pointer to one past the top-most value on the stack. - Value* stackTop; - - // The number of allocated slots in the stack array. - int stackCapacity; - - // The stack of call frames. This is a dynamic array that grows as needed but - // never shrinks. - CallFrame* frames; - - // The number of frames currently in use in [frames]. - int numFrames; - - // The number of [frames] allocated. - int frameCapacity; - - // Pointer to the first node in the linked list of open upvalues that are - // pointing to values still on the stack. The head of the list will be the - // upvalue closest to the top of the stack, and then the list works downwards. - ObjUpvalue* openUpvalues; - - // The fiber that ran this one. If this fiber is yielded, control will resume - // to this one. May be `NULL`. - struct sObjFiber* caller; - - // If the fiber failed because of a runtime error, this will contain the - // error object. Otherwise, it will be null. - Value error; - - FiberState state; -} ObjFiber; - -typedef enum -{ - // A primitive method implemented in C in the VM. Unlike foreign methods, - // this can directly manipulate the fiber's stack. - METHOD_PRIMITIVE, - - // A primitive that handles .call on Fn. - METHOD_FUNCTION_CALL, - - // A externally-defined C method. - METHOD_FOREIGN, - - // A normal user-defined method. - METHOD_BLOCK, - - // No method for the given symbol. - METHOD_NONE -} MethodType; - -typedef struct -{ - MethodType type; - - // The method function itself. The [type] determines which field of the union - // is used. - union - { - Primitive primitive; - WrenForeignMethodFn foreign; - ObjClosure* closure; - } as; -} Method; - -DECLARE_BUFFER(Method, Method); - -struct sObjClass -{ - Obj obj; - ObjClass* superclass; - - // The number of fields needed for an instance of this class, including all - // of its superclass fields. - int numFields; - - // The table of methods that are defined in or inherited by this class. - // Methods are called by symbol, and the symbol directly maps to an index in - // this table. This makes method calls fast at the expense of empty cells in - // the list for methods the class doesn't support. - // - // You can think of it as a hash table that never has collisions but has a - // really low load factor. Since methods are pretty small (just a type and a - // pointer), this should be a worthwhile trade-off. - MethodBuffer methods; - - // The name of the class. - ObjString* name; - - // The ClassAttribute for the class, if any - Value attributes; -}; - -typedef struct -{ - Obj obj; - uint8_t data[FLEXIBLE_ARRAY]; -} ObjForeign; - -typedef struct -{ - Obj obj; - Value fields[FLEXIBLE_ARRAY]; -} ObjInstance; - -typedef struct -{ - Obj obj; - - // The elements in the list. - ValueBuffer elements; -} ObjList; - -typedef struct -{ - // The entry's key, or UNDEFINED_VAL if the entry is not in use. - Value key; - - // The value associated with the key. If the key is UNDEFINED_VAL, this will - // be false to indicate an open available entry or true to indicate a - // tombstone -- an entry that was previously in use but was then deleted. - Value value; -} MapEntry; - -// A hash table mapping keys to values. -// -// We use something very simple: open addressing with linear probing. The hash -// table is an array of entries. Each entry is a key-value pair. If the key is -// the special UNDEFINED_VAL, it indicates no value is currently in that slot. -// Otherwise, it's a valid key, and the value is the value associated with it. -// -// When entries are added, the array is dynamically scaled by GROW_FACTOR to -// keep the number of filled slots under MAP_LOAD_PERCENT. Likewise, if the map -// gets empty enough, it will be resized to a smaller array. When this happens, -// all existing entries are rehashed and re-added to the new array. -// -// When an entry is removed, its slot is replaced with a "tombstone". This is an -// entry whose key is UNDEFINED_VAL and whose value is TRUE_VAL. When probing -// for a key, we will continue past tombstones, because the desired key may be -// found after them if the key that was removed was part of a prior collision. -// When the array gets resized, all tombstones are discarded. -typedef struct -{ - Obj obj; - - // The number of entries allocated. - uint32_t capacity; - - // The number of entries in the map. - uint32_t count; - - // Pointer to a contiguous array of [capacity] entries. - MapEntry* entries; -} ObjMap; - -typedef struct -{ - Obj obj; - - // The beginning of the range. - double from; - - // The end of the range. May be greater or less than [from]. - double to; - - // True if [to] is included in the range. - bool isInclusive; -} ObjRange; - -// An IEEE 754 double-precision float is a 64-bit value with bits laid out like: -// -// 1 Sign bit -// | 11 Exponent bits -// | | 52 Mantissa (i.e. fraction) bits -// | | | -// S[Exponent-][Mantissa------------------------------------------] -// -// The details of how these are used to represent numbers aren't really -// relevant here as long we don't interfere with them. The important bit is NaN. -// -// An IEEE double can represent a few magical values like NaN ("not a number"), -// Infinity, and -Infinity. A NaN is any value where all exponent bits are set: -// -// v--NaN bits -// -11111111111---------------------------------------------------- -// -// Here, "-" means "doesn't matter". Any bit sequence that matches the above is -// a NaN. With all of those "-", it obvious there are a *lot* of different -// bit patterns that all mean the same thing. NaN tagging takes advantage of -// this. We'll use those available bit patterns to represent things other than -// numbers without giving up any valid numeric values. -// -// NaN values come in two flavors: "signalling" and "quiet". The former are -// intended to halt execution, while the latter just flow through arithmetic -// operations silently. We want the latter. Quiet NaNs are indicated by setting -// the highest mantissa bit: -// -// v--Highest mantissa bit -// -[NaN ]1--------------------------------------------------- -// -// If all of the NaN bits are set, it's not a number. Otherwise, it is. -// That leaves all of the remaining bits as available for us to play with. We -// stuff a few different kinds of things here: special singleton values like -// "true", "false", and "null", and pointers to objects allocated on the heap. -// We'll use the sign bit to distinguish singleton values from pointers. If -// it's set, it's a pointer. -// -// v--Pointer or singleton? -// S[NaN ]1--------------------------------------------------- -// -// For singleton values, we just enumerate the different values. We'll use the -// low bits of the mantissa for that, and only need a few: -// -// 3 Type bits--v -// 0[NaN ]1------------------------------------------------[T] -// -// For pointers, we are left with 51 bits of mantissa to store an address. -// That's more than enough room for a 32-bit address. Even 64-bit machines -// only actually use 48 bits for addresses, so we've got plenty. We just stuff -// the address right into the mantissa. -// -// Ta-da, double precision numbers, pointers, and a bunch of singleton values, -// all stuffed into a single 64-bit sequence. Even better, we don't have to -// do any masking or work to extract number values: they are unmodified. This -// means math on numbers is fast. -#if WREN_NAN_TAGGING - -// A mask that selects the sign bit. -#define SIGN_BIT ((uint64_t)1 << 63) - -// The bits that must be set to indicate a quiet NaN. -#define QNAN ((uint64_t)0x7ffc000000000000) - -// If the NaN bits are set, it's not a number. -#define IS_NUM(value) (((value) & QNAN) != QNAN) - -// An object pointer is a NaN with a set sign bit. -#define IS_OBJ(value) (((value) & (QNAN | SIGN_BIT)) == (QNAN | SIGN_BIT)) - -#define IS_FALSE(value) ((value) == FALSE_VAL) -#define IS_NULL(value) ((value) == NULL_VAL) -#define IS_UNDEFINED(value) ((value) == UNDEFINED_VAL) - -// Masks out the tag bits used to identify the singleton value. -#define MASK_TAG (7) - -// Tag values for the different singleton values. -#define TAG_NAN (0) -#define TAG_NULL (1) -#define TAG_FALSE (2) -#define TAG_TRUE (3) -#define TAG_UNDEFINED (4) -#define TAG_UNUSED2 (5) -#define TAG_UNUSED3 (6) -#define TAG_UNUSED4 (7) - -// Value -> 0 or 1. -#define AS_BOOL(value) ((value) == TRUE_VAL) - -// Value -> Obj*. -#define AS_OBJ(value) ((Obj*)(uintptr_t)((value) & ~(SIGN_BIT | QNAN))) - -// Singleton values. -#define NULL_VAL ((Value)(uint64_t)(QNAN | TAG_NULL)) -#define FALSE_VAL ((Value)(uint64_t)(QNAN | TAG_FALSE)) -#define TRUE_VAL ((Value)(uint64_t)(QNAN | TAG_TRUE)) -#define UNDEFINED_VAL ((Value)(uint64_t)(QNAN | TAG_UNDEFINED)) - -// Gets the singleton type tag for a Value (which must be a singleton). -#define GET_TAG(value) ((int)((value) & MASK_TAG)) - -#else - -// Value -> 0 or 1. -#define AS_BOOL(value) ((value).type == VAL_TRUE) - -// Value -> Obj*. -#define AS_OBJ(v) ((v).as.obj) - -// Determines if [value] is a garbage-collected object or not. -#define IS_OBJ(value) ((value).type == VAL_OBJ) - -#define IS_FALSE(value) ((value).type == VAL_FALSE) -#define IS_NULL(value) ((value).type == VAL_NULL) -#define IS_NUM(value) ((value).type == VAL_NUM) -#define IS_UNDEFINED(value) ((value).type == VAL_UNDEFINED) - -// Singleton values. -#define FALSE_VAL ((Value){ VAL_FALSE, { 0 } }) -#define NULL_VAL ((Value){ VAL_NULL, { 0 } }) -#define TRUE_VAL ((Value){ VAL_TRUE, { 0 } }) -#define UNDEFINED_VAL ((Value){ VAL_UNDEFINED, { 0 } }) - -#endif - -// Creates a new "raw" class. It has no metaclass or superclass whatsoever. -// This is only used for bootstrapping the initial Object and Class classes, -// which are a little special. -ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name); - -// Makes [superclass] the superclass of [subclass], and causes subclass to -// inherit its methods. This should be called before any methods are defined -// on subclass. -void wrenBindSuperclass(WrenVM* vm, ObjClass* subclass, ObjClass* superclass); - -// Creates a new class object as well as its associated metaclass. -ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields, - ObjString* name); - -void wrenBindMethod(WrenVM* vm, ObjClass* classObj, int symbol, Method method); - -// Creates a new closure object that invokes [fn]. Allocates room for its -// upvalues, but assumes outside code will populate it. -ObjClosure* wrenNewClosure(WrenVM* vm, ObjFn* fn); - -// Creates a new fiber object that will invoke [closure]. -ObjFiber* wrenNewFiber(WrenVM* vm, ObjClosure* closure); - -// Adds a new [CallFrame] to [fiber] invoking [closure] whose stack starts at -// [stackStart]. -static inline void wrenAppendCallFrame(WrenVM* vm, ObjFiber* fiber, - ObjClosure* closure, Value* stackStart) -{ - // The caller should have ensured we already have enough capacity. - ASSERT(fiber->frameCapacity > fiber->numFrames, "No memory for call frame."); - - CallFrame* frame = &fiber->frames[fiber->numFrames++]; - frame->stackStart = stackStart; - frame->closure = closure; - frame->ip = closure->fn->code.data; -} - -// Ensures [fiber]'s stack has at least [needed] slots. -void wrenEnsureStack(WrenVM* vm, ObjFiber* fiber, int needed); - -static inline bool wrenHasError(const ObjFiber* fiber) -{ - return !IS_NULL(fiber->error); -} - -ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size); - -// Creates a new empty function. Before being used, it must have code, -// constants, etc. added to it. -ObjFn* wrenNewFunction(WrenVM* vm, ObjModule* module, int maxSlots); - -void wrenFunctionBindName(WrenVM* vm, ObjFn* fn, const char* name, int length); - -// Creates a new instance of the given [classObj]. -Value wrenNewInstance(WrenVM* vm, ObjClass* classObj); - -// Creates a new list with [numElements] elements (which are left -// uninitialized.) -ObjList* wrenNewList(WrenVM* vm, uint32_t numElements); - -// Inserts [value] in [list] at [index], shifting down the other elements. -void wrenListInsert(WrenVM* vm, ObjList* list, Value value, uint32_t index); - -// Removes and returns the item at [index] from [list]. -Value wrenListRemoveAt(WrenVM* vm, ObjList* list, uint32_t index); - -// Searches for [value] in [list], returns the index or -1 if not found. -int wrenListIndexOf(WrenVM* vm, ObjList* list, Value value); - -// Creates a new empty map. -ObjMap* wrenNewMap(WrenVM* vm); - -// Validates that [arg] is a valid object for use as a map key. Returns true if -// it is and returns false otherwise. Use validateKey usually, for a runtime error. -// This separation exists to aid the API in surfacing errors to the developer as well. -static inline bool wrenMapIsValidKey(Value arg); - -// Looks up [key] in [map]. If found, returns the value. Otherwise, returns -// `UNDEFINED_VAL`. -Value wrenMapGet(ObjMap* map, Value key); - -// Associates [key] with [value] in [map]. -void wrenMapSet(WrenVM* vm, ObjMap* map, Value key, Value value); - -void wrenMapClear(WrenVM* vm, ObjMap* map); - -// Removes [key] from [map], if present. Returns the value for the key if found -// or `NULL_VAL` otherwise. -Value wrenMapRemoveKey(WrenVM* vm, ObjMap* map, Value key); - -// Creates a new module. -ObjModule* wrenNewModule(WrenVM* vm, ObjString* name); - -// Creates a new range from [from] to [to]. -Value wrenNewRange(WrenVM* vm, double from, double to, bool isInclusive); - -// Creates a new string object and copies [text] into it. -// -// [text] must be non-NULL. -Value wrenNewString(WrenVM* vm, const char* text); - -// Creates a new string object of [length] and copies [text] into it. -// -// [text] may be NULL if [length] is zero. -Value wrenNewStringLength(WrenVM* vm, const char* text, size_t length); - -// Creates a new string object by taking a range of characters from [source]. -// The range starts at [start], contains [count] bytes, and increments by -// [step]. -Value wrenNewStringFromRange(WrenVM* vm, ObjString* source, int start, - uint32_t count, int step); - -// Produces a string representation of [value]. -Value wrenNumToString(WrenVM* vm, double value); - -// Creates a new formatted string from [format] and any additional arguments -// used in the format string. -// -// This is a very restricted flavor of formatting, intended only for internal -// use by the VM. Two formatting characters are supported, each of which reads -// the next argument as a certain type: -// -// $ - A C string. -// @ - A Wren string object. -Value wrenStringFormat(WrenVM* vm, const char* format, ...); - -// Creates a new string containing the UTF-8 encoding of [value]. -Value wrenStringFromCodePoint(WrenVM* vm, int value); - -// Creates a new string from the integer representation of a byte -Value wrenStringFromByte(WrenVM* vm, uint8_t value); - -// Creates a new string containing the code point in [string] starting at byte -// [index]. If [index] points into the middle of a UTF-8 sequence, returns an -// empty string. -Value wrenStringCodePointAt(WrenVM* vm, ObjString* string, uint32_t index); - -// Search for the first occurence of [needle] within [haystack] and returns its -// zero-based offset. Returns `UINT32_MAX` if [haystack] does not contain -// [needle]. -uint32_t wrenStringFind(ObjString* haystack, ObjString* needle, - uint32_t startIndex); - -// Returns true if [a] and [b] represent the same string. -static inline bool wrenStringEqualsCString(const ObjString* a, - const char* b, size_t length) -{ - return a->length == length && memcmp(a->value, b, length) == 0; -} - -// Creates a new open upvalue pointing to [value] on the stack. -ObjUpvalue* wrenNewUpvalue(WrenVM* vm, Value* value); - -// Mark [obj] as reachable and still in use. This should only be called -// during the sweep phase of a garbage collection. -void wrenGrayObj(WrenVM* vm, Obj* obj); - -// Mark [value] as reachable and still in use. This should only be called -// during the sweep phase of a garbage collection. -void wrenGrayValue(WrenVM* vm, Value value); - -// Mark the values in [buffer] as reachable and still in use. This should only -// be called during the sweep phase of a garbage collection. -void wrenGrayBuffer(WrenVM* vm, ValueBuffer* buffer); - -// Processes every object in the gray stack until all reachable objects have -// been marked. After that, all objects are either white (freeable) or black -// (in use and fully traversed). -void wrenBlackenObjects(WrenVM* vm); - -// Releases all memory owned by [obj], including [obj] itself. -void wrenFreeObj(WrenVM* vm, Obj* obj); - -// Returns the class of [value]. -// -// Unlike wrenGetClassInline in wren_vm.h, this is not inlined. Inlining helps -// performance (significantly) in some cases, but degrades it in others. The -// ones used by the implementation were chosen to give the best results in the -// benchmarks. -ObjClass* wrenGetClass(WrenVM* vm, Value value); - -// Returns true if [a] and [b] are strictly the same value. This is identity -// for object values, and value equality for unboxed values. -static inline bool wrenValuesSame(Value a, Value b) -{ -#if WREN_NAN_TAGGING - // Value types have unique bit representations and we compare object types - // by identity (i.e. pointer), so all we need to do is compare the bits. - return a == b; -#else - if (a.type != b.type) return false; - if (a.type == VAL_NUM) return a.as.num == b.as.num; - return a.as.obj == b.as.obj; -#endif -} - -// Returns true if [a] and [b] are equivalent. Immutable values (null, bools, -// numbers, ranges, and strings) are equal if they have the same data. All -// other values are equal if they are identical objects. -bool wrenValuesEqual(Value a, Value b); - -// Returns true if [value] is a bool. Do not call this directly, instead use -// [IS_BOOL]. -static inline bool wrenIsBool(Value value) -{ -#if WREN_NAN_TAGGING - return value == TRUE_VAL || value == FALSE_VAL; -#else - return value.type == VAL_FALSE || value.type == VAL_TRUE; -#endif -} - -// Returns true if [value] is an object of type [type]. Do not call this -// directly, instead use the [IS___] macro for the type in question. -static inline bool wrenIsObjType(Value value, ObjType type) -{ - return IS_OBJ(value) && AS_OBJ(value)->type == type; -} - -// Converts the raw object pointer [obj] to a [Value]. -static inline Value wrenObjectToValue(Obj* obj) -{ -#if WREN_NAN_TAGGING - // The triple casting is necessary here to satisfy some compilers: - // 1. (uintptr_t) Convert the pointer to a number of the right size. - // 2. (uint64_t) Pad it up to 64 bits in 32-bit builds. - // 3. Or in the bits to make a tagged Nan. - // 4. Cast to a typedef'd value. - return (Value)(SIGN_BIT | QNAN | (uint64_t)(uintptr_t)(obj)); -#else - Value value; - value.type = VAL_OBJ; - value.as.obj = obj; - return value; -#endif -} - -// Interprets [value] as a [double]. -static inline double wrenValueToNum(Value value) -{ -#if WREN_NAN_TAGGING - return wrenDoubleFromBits(value); -#else - return value.as.num; -#endif -} - -// Converts [num] to a [Value]. -static inline Value wrenNumToValue(double num) -{ -#if WREN_NAN_TAGGING - return wrenDoubleToBits(num); -#else - Value value; - value.type = VAL_NUM; - value.as.num = num; - return value; -#endif -} - -static inline bool wrenMapIsValidKey(Value arg) -{ - return IS_BOOL(arg) - || IS_CLASS(arg) - || IS_NULL(arg) - || IS_NUM(arg) - || IS_RANGE(arg) - || IS_STRING(arg); -} - -#endif -// End file "wren_value.h" -// Begin file "wren_vm.h" -#ifndef wren_vm_h -#define wren_vm_h - -// Begin file "wren_compiler.h" -#ifndef wren_compiler_h -#define wren_compiler_h - - -typedef struct sCompiler Compiler; - -// This module defines the compiler for Wren. It takes a string of source code -// and lexes, parses, and compiles it. Wren uses a single-pass compiler. It -// does not build an actual AST during parsing and then consume that to -// generate code. Instead, the parser directly emits bytecode. -// -// This forces a few restrictions on the grammar and semantics of the language. -// Things like forward references and arbitrary lookahead are much harder. We -// get a lot in return for that, though. -// -// The implementation is much simpler since we don't need to define a bunch of -// AST data structures. More so, we don't have to deal with managing memory for -// AST objects. The compiler does almost no dynamic allocation while running. -// -// Compilation is also faster since we don't create a bunch of temporary data -// structures and destroy them after generating code. - -// Compiles [source], a string of Wren source code located in [module], to an -// [ObjFn] that will execute that code when invoked. Returns `NULL` if the -// source contains any syntax errors. -// -// If [isExpression] is `true`, [source] should be a single expression, and -// this compiles it to a function that evaluates and returns that expression. -// Otherwise, [source] should be a series of top level statements. -// -// If [printErrors] is `true`, any compile errors are output to stderr. -// Otherwise, they are silently discarded. -ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source, - bool isExpression, bool printErrors); - -// When a class is defined, its superclass is not known until runtime since -// class definitions are just imperative statements. Most of the bytecode for a -// a method doesn't care, but there are two places where it matters: -// -// - To load or store a field, we need to know the index of the field in the -// instance's field array. We need to adjust this so that subclass fields -// are positioned after superclass fields, and we don't know this until the -// superclass is known. -// -// - Superclass calls need to know which superclass to dispatch to. -// -// We could handle this dynamically, but that adds overhead. Instead, when a -// method is bound, we walk the bytecode for the function and patch it up. -void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn); - -// Reaches all of the heap-allocated objects in use by [compiler] (and all of -// its parents) so that they are not collected by the GC. -void wrenMarkCompiler(WrenVM* vm, Compiler* compiler); - -#endif -// End file "wren_compiler.h" - -// The maximum number of temporary objects that can be made visible to the GC -// at one time. -#define WREN_MAX_TEMP_ROOTS 8 - -typedef enum -{ - #define OPCODE(name, _) CODE_##name, -// Begin file "wren_opcodes.h" -// This defines the bytecode instructions used by the VM. It does so by invoking -// an OPCODE() macro which is expected to be defined at the point that this is -// included. (See: http://en.wikipedia.org/wiki/X_Macro for more.) -// -// The first argument is the name of the opcode. The second is its "stack -// effect" -- the amount that the op code changes the size of the stack. A -// stack effect of 1 means it pushes a value and the stack grows one larger. -// -2 means it pops two values, etc. -// -// Note that the order of instructions here affects the order of the dispatch -// table in the VM's interpreter loop. That in turn affects caching which -// affects overall performance. Take care to run benchmarks if you change the -// order here. - -// Load the constant at index [arg]. -OPCODE(CONSTANT, 1) - -// Push null onto the stack. -OPCODE(NULL, 1) - -// Push false onto the stack. -OPCODE(FALSE, 1) - -// Push true onto the stack. -OPCODE(TRUE, 1) - -// Pushes the value in the given local slot. -OPCODE(LOAD_LOCAL_0, 1) -OPCODE(LOAD_LOCAL_1, 1) -OPCODE(LOAD_LOCAL_2, 1) -OPCODE(LOAD_LOCAL_3, 1) -OPCODE(LOAD_LOCAL_4, 1) -OPCODE(LOAD_LOCAL_5, 1) -OPCODE(LOAD_LOCAL_6, 1) -OPCODE(LOAD_LOCAL_7, 1) -OPCODE(LOAD_LOCAL_8, 1) - -// Note: The compiler assumes the following _STORE instructions always -// immediately follow their corresponding _LOAD ones. - -// Pushes the value in local slot [arg]. -OPCODE(LOAD_LOCAL, 1) - -// Stores the top of stack in local slot [arg]. Does not pop it. -OPCODE(STORE_LOCAL, 0) - -// Pushes the value in upvalue [arg]. -OPCODE(LOAD_UPVALUE, 1) - -// Stores the top of stack in upvalue [arg]. Does not pop it. -OPCODE(STORE_UPVALUE, 0) - -// Pushes the value of the top-level variable in slot [arg]. -OPCODE(LOAD_MODULE_VAR, 1) - -// Stores the top of stack in top-level variable slot [arg]. Does not pop it. -OPCODE(STORE_MODULE_VAR, 0) - -// Pushes the value of the field in slot [arg] of the receiver of the current -// function. This is used for regular field accesses on "this" directly in -// methods. This instruction is faster than the more general CODE_LOAD_FIELD -// instruction. -OPCODE(LOAD_FIELD_THIS, 1) - -// Stores the top of the stack in field slot [arg] in the receiver of the -// current value. Does not pop the value. This instruction is faster than the -// more general CODE_LOAD_FIELD instruction. -OPCODE(STORE_FIELD_THIS, 0) - -// Pops an instance and pushes the value of the field in slot [arg] of it. -OPCODE(LOAD_FIELD, 0) - -// Pops an instance and stores the subsequent top of stack in field slot -// [arg] in it. Does not pop the value. -OPCODE(STORE_FIELD, -1) - -// Pop and discard the top of stack. -OPCODE(POP, -1) - -// Invoke the method with symbol [arg]. The number indicates the number of -// arguments (not including the receiver). -OPCODE(CALL_0, 0) -OPCODE(CALL_1, -1) -OPCODE(CALL_2, -2) -OPCODE(CALL_3, -3) -OPCODE(CALL_4, -4) -OPCODE(CALL_5, -5) -OPCODE(CALL_6, -6) -OPCODE(CALL_7, -7) -OPCODE(CALL_8, -8) -OPCODE(CALL_9, -9) -OPCODE(CALL_10, -10) -OPCODE(CALL_11, -11) -OPCODE(CALL_12, -12) -OPCODE(CALL_13, -13) -OPCODE(CALL_14, -14) -OPCODE(CALL_15, -15) -OPCODE(CALL_16, -16) - -// Invoke a superclass method with symbol [arg]. The number indicates the -// number of arguments (not including the receiver). -OPCODE(SUPER_0, 0) -OPCODE(SUPER_1, -1) -OPCODE(SUPER_2, -2) -OPCODE(SUPER_3, -3) -OPCODE(SUPER_4, -4) -OPCODE(SUPER_5, -5) -OPCODE(SUPER_6, -6) -OPCODE(SUPER_7, -7) -OPCODE(SUPER_8, -8) -OPCODE(SUPER_9, -9) -OPCODE(SUPER_10, -10) -OPCODE(SUPER_11, -11) -OPCODE(SUPER_12, -12) -OPCODE(SUPER_13, -13) -OPCODE(SUPER_14, -14) -OPCODE(SUPER_15, -15) -OPCODE(SUPER_16, -16) - -// Jump the instruction pointer [arg] forward. -OPCODE(JUMP, 0) - -// Jump the instruction pointer [arg] backward. -OPCODE(LOOP, 0) - -// Pop and if not truthy then jump the instruction pointer [arg] forward. -OPCODE(JUMP_IF, -1) - -// If the top of the stack is false, jump [arg] forward. Otherwise, pop and -// continue. -OPCODE(AND, -1) - -// If the top of the stack is non-false, jump [arg] forward. Otherwise, pop -// and continue. -OPCODE(OR, -1) - -// Close the upvalue for the local on the top of the stack, then pop it. -OPCODE(CLOSE_UPVALUE, -1) - -// Exit from the current function and return the value on the top of the -// stack. -OPCODE(RETURN, 0) - -// Creates a closure for the function stored at [arg] in the constant table. -// -// Following the function argument is a number of arguments, two for each -// upvalue. The first is true if the variable being captured is a local (as -// opposed to an upvalue), and the second is the index of the local or -// upvalue being captured. -// -// Pushes the created closure. -OPCODE(CLOSURE, 1) - -// Creates a new instance of a class. -// -// Assumes the class object is in slot zero, and replaces it with the new -// uninitialized instance of that class. This opcode is only emitted by the -// compiler-generated constructor metaclass methods. -OPCODE(CONSTRUCT, 0) - -// Creates a new instance of a foreign class. -// -// Assumes the class object is in slot zero, and replaces it with the new -// uninitialized instance of that class. This opcode is only emitted by the -// compiler-generated constructor metaclass methods. -OPCODE(FOREIGN_CONSTRUCT, 0) - -// Creates a class. Top of stack is the superclass. Below that is a string for -// the name of the class. Byte [arg] is the number of fields in the class. -OPCODE(CLASS, -1) - -// Ends a class. -// Atm the stack contains the class and the ClassAttributes (or null). -OPCODE(END_CLASS, -2) - -// Creates a foreign class. Top of stack is the superclass. Below that is a -// string for the name of the class. -OPCODE(FOREIGN_CLASS, -1) - -// Define a method for symbol [arg]. The class receiving the method is popped -// off the stack, then the function defining the body is popped. -// -// If a foreign method is being defined, the "function" will be a string -// identifying the foreign method. Otherwise, it will be a function or -// closure. -OPCODE(METHOD_INSTANCE, -2) - -// Define a method for symbol [arg]. The class whose metaclass will receive -// the method is popped off the stack, then the function defining the body is -// popped. -// -// If a foreign method is being defined, the "function" will be a string -// identifying the foreign method. Otherwise, it will be a function or -// closure. -OPCODE(METHOD_STATIC, -2) - -// This is executed at the end of the module's body. Pushes NULL onto the stack -// as the "return value" of the import statement and stores the module as the -// most recently imported one. -OPCODE(END_MODULE, 1) - -// Import a module whose name is the string stored at [arg] in the constant -// table. -// -// Pushes null onto the stack so that the fiber for the imported module can -// replace that with a dummy value when it returns. (Fibers always return a -// value when resuming a caller.) -OPCODE(IMPORT_MODULE, 1) - -// Import a variable from the most recently imported module. The name of the -// variable to import is at [arg] in the constant table. Pushes the loaded -// variable's value. -OPCODE(IMPORT_VARIABLE, 1) - -// This pseudo-instruction indicates the end of the bytecode. It should -// always be preceded by a `CODE_RETURN`, so is never actually executed. -OPCODE(END, 0) -// End file "wren_opcodes.h" - #undef OPCODE -} Code; - -// A handle to a value, basically just a linked list of extra GC roots. -// -// Note that even non-heap-allocated values can be stored here. -struct WrenHandle -{ - Value value; - - WrenHandle* prev; - WrenHandle* next; -}; - -struct WrenVM -{ - ObjClass* boolClass; - ObjClass* classClass; - ObjClass* fiberClass; - ObjClass* fnClass; - ObjClass* listClass; - ObjClass* mapClass; - ObjClass* nullClass; - ObjClass* numClass; - ObjClass* objectClass; - ObjClass* rangeClass; - ObjClass* stringClass; - - // The fiber that is currently running. - ObjFiber* fiber; - - // The loaded modules. Each key is an ObjString (except for the main module, - // whose key is null) for the module's name and the value is the ObjModule - // for the module. - ObjMap* modules; - - // The most recently imported module. More specifically, the module whose - // code has most recently finished executing. - // - // Not treated like a GC root since the module is already in [modules]. - ObjModule* lastModule; - - // Memory management data: - - // The number of bytes that are known to be currently allocated. Includes all - // memory that was proven live after the last GC, as well as any new bytes - // that were allocated since then. Does *not* include bytes for objects that - // were freed since the last GC. - size_t bytesAllocated; - - // The number of total allocated bytes that will trigger the next GC. - size_t nextGC; - - // The first object in the linked list of all currently allocated objects. - Obj* first; - - // The "gray" set for the garbage collector. This is the stack of unprocessed - // objects while a garbage collection pass is in process. - Obj** gray; - int grayCount; - int grayCapacity; - - // The list of temporary roots. This is for temporary or new objects that are - // not otherwise reachable but should not be collected. - // - // They are organized as a stack of pointers stored in this array. This - // implies that temporary roots need to have stack semantics: only the most - // recently pushed object can be released. - Obj* tempRoots[WREN_MAX_TEMP_ROOTS]; - - int numTempRoots; - - // Pointer to the first node in the linked list of active handles or NULL if - // there are none. - WrenHandle* handles; - - // Pointer to the bottom of the range of stack slots available for use from - // the C API. During a foreign method, this will be in the stack of the fiber - // that is executing a method. - // - // If not in a foreign method, this is initially NULL. If the user requests - // slots by calling wrenEnsureSlots(), a stack is created and this is - // initialized. - Value* apiStack; - - WrenConfiguration config; - - // Compiler and debugger data: - - // The compiler that is currently compiling code. This is used so that heap - // allocated objects used by the compiler can be found if a GC is kicked off - // in the middle of a compile. - Compiler* compiler; - - // There is a single global symbol table for all method names on all classes. - // Method calls are dispatched directly by index in this table. - SymbolTable methodNames; -}; - -// A generic allocation function that handles all explicit memory management. -// It's used like so: -// -// - To allocate new memory, [memory] is NULL and [oldSize] is zero. It should -// return the allocated memory or NULL on failure. -// -// - To attempt to grow an existing allocation, [memory] is the memory, -// [oldSize] is its previous size, and [newSize] is the desired size. -// It should return [memory] if it was able to grow it in place, or a new -// pointer if it had to move it. -// -// - To shrink memory, [memory], [oldSize], and [newSize] are the same as above -// but it will always return [memory]. -// -// - To free memory, [memory] will be the memory to free and [newSize] and -// [oldSize] will be zero. It should return NULL. -void* wrenReallocate(WrenVM* vm, void* memory, size_t oldSize, size_t newSize); - -// Invoke the finalizer for the foreign object referenced by [foreign]. -void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign); - -// Creates a new [WrenHandle] for [value]. -WrenHandle* wrenMakeHandle(WrenVM* vm, Value value); - -// Compile [source] in the context of [module] and wrap in a fiber that can -// execute it. -// -// Returns NULL if a compile error occurred. -ObjClosure* wrenCompileSource(WrenVM* vm, const char* module, - const char* source, bool isExpression, - bool printErrors); - -// Looks up a variable from a previously-loaded module. -// -// Aborts the current fiber if the module or variable could not be found. -Value wrenGetModuleVariable(WrenVM* vm, Value moduleName, Value variableName); - -// Returns the value of the module-level variable named [name] in the main -// module. -Value wrenFindVariable(WrenVM* vm, ObjModule* module, const char* name); - -// Adds a new implicitly declared top-level variable named [name] to [module] -// based on a use site occurring on [line]. -// -// Does not check to see if a variable with that name is already declared or -// defined. Returns the symbol for the new variable or -2 if there are too many -// variables defined. -int wrenDeclareVariable(WrenVM* vm, ObjModule* module, const char* name, - size_t length, int line); - -// Adds a new top-level variable named [name] to [module], and optionally -// populates line with the line of the implicit first use (line can be NULL). -// -// Returns the symbol for the new variable, -1 if a variable with the given name -// is already defined, or -2 if there are too many variables defined. -// Returns -3 if this is a top-level lowercase variable (localname) that was -// used before being defined. -int wrenDefineVariable(WrenVM* vm, ObjModule* module, const char* name, - size_t length, Value value, int* line); - -// Pushes [closure] onto [fiber]'s callstack to invoke it. Expects [numArgs] -// arguments (including the receiver) to be on the top of the stack already. -static inline void wrenCallFunction(WrenVM* vm, ObjFiber* fiber, - ObjClosure* closure, int numArgs) -{ - // Grow the call frame array if needed. - if (fiber->numFrames + 1 > fiber->frameCapacity) - { - int max = fiber->frameCapacity * 2; - fiber->frames = (CallFrame*)wrenReallocate(vm, fiber->frames, - sizeof(CallFrame) * fiber->frameCapacity, sizeof(CallFrame) * max); - fiber->frameCapacity = max; - } - - // Grow the stack if needed. - int stackSize = (int)(fiber->stackTop - fiber->stack); - int needed = stackSize + closure->fn->maxSlots; - wrenEnsureStack(vm, fiber, needed); - - wrenAppendCallFrame(vm, fiber, closure, fiber->stackTop - numArgs); -} - -// Marks [obj] as a GC root so that it doesn't get collected. -void wrenPushRoot(WrenVM* vm, Obj* obj); - -// Removes the most recently pushed temporary root. -void wrenPopRoot(WrenVM* vm); - -// Returns the class of [value]. -// -// Defined here instead of in wren_value.h because it's critical that this be -// inlined. That means it must be defined in the header, but the wren_value.h -// header doesn't have a full definitely of WrenVM yet. -static inline ObjClass* wrenGetClassInline(WrenVM* vm, Value value) -{ - if (IS_NUM(value)) return vm->numClass; - if (IS_OBJ(value)) return AS_OBJ(value)->classObj; - -#if WREN_NAN_TAGGING - switch (GET_TAG(value)) - { - case TAG_FALSE: return vm->boolClass; break; - case TAG_NAN: return vm->numClass; break; - case TAG_NULL: return vm->nullClass; break; - case TAG_TRUE: return vm->boolClass; break; - case TAG_UNDEFINED: UNREACHABLE(); - } -#else - switch (value.type) - { - case VAL_FALSE: return vm->boolClass; - case VAL_NULL: return vm->nullClass; - case VAL_NUM: return vm->numClass; - case VAL_TRUE: return vm->boolClass; - case VAL_OBJ: return AS_OBJ(value)->classObj; - case VAL_UNDEFINED: UNREACHABLE(); - } -#endif - - UNREACHABLE(); - return NULL; -} - -// Returns `true` if [name] is a local variable name (starts with a lowercase -// letter). -static inline bool wrenIsLocalName(const char* name) -{ - return name[0] >= 'a' && name[0] <= 'z'; -} - -static inline bool wrenIsFalsyValue(Value value) -{ - return IS_FALSE(value) || IS_NULL(value); -} - -#endif -// End file "wren_vm.h" - -// Prints the stack trace for the current fiber. -// -// Used when a fiber throws a runtime error which is not caught. -void wrenDebugPrintStackTrace(WrenVM* vm); - -// The "dump" functions are used for debugging Wren itself. Normal code paths -// will not call them unless one of the various DEBUG_ flags is enabled. - -// Prints a representation of [value] to stdout. -void wrenDumpValue(Value value); - -// Prints a representation of the bytecode for [fn] at instruction [i]. -int wrenDumpInstruction(WrenVM* vm, ObjFn* fn, int i); - -// Prints the disassembled code for [fn] to stdout. -void wrenDumpCode(WrenVM* vm, ObjFn* fn); - -// Prints the contents of the current stack for [fiber] to stdout. -void wrenDumpStack(ObjFiber* fiber); - -#endif -// End file "wren_debug.h" -// Begin file "wren_debug.c" -#include - - -void wrenDebugPrintStackTrace(WrenVM* vm) -{ - // Bail if the host doesn't enable printing errors. - if (vm->config.errorFn == NULL) return; - - ObjFiber* fiber = vm->fiber; - if (IS_STRING(fiber->error)) - { - vm->config.errorFn(vm, WREN_ERROR_RUNTIME, - NULL, -1, AS_CSTRING(fiber->error)); - } - else - { - // TODO: Print something a little useful here. Maybe the name of the error's - // class? - vm->config.errorFn(vm, WREN_ERROR_RUNTIME, - NULL, -1, "[error object]"); - } - - for (int i = fiber->numFrames - 1; i >= 0; i--) - { - CallFrame* frame = &fiber->frames[i]; - ObjFn* fn = frame->closure->fn; - - // Skip over stub functions for calling methods from the C API. - if (fn->module == NULL) continue; - - // The built-in core module has no name. We explicitly omit it from stack - // traces since we don't want to highlight to a user the implementation - // detail of what part of the core module is written in C and what is Wren. - if (fn->module->name == NULL) continue; - - // -1 because IP has advanced past the instruction that it just executed. - int line = fn->debug->sourceLines.data[frame->ip - fn->code.data - 1]; - vm->config.errorFn(vm, WREN_ERROR_STACK_TRACE, - fn->module->name->value, line, - fn->debug->name); - } -} - -static void dumpObject(Obj* obj) -{ - switch (obj->type) - { - case OBJ_CLASS: - printf("[class %s %p]", ((ObjClass*)obj)->name->value, obj); - break; - case OBJ_CLOSURE: printf("[closure %p]", obj); break; - case OBJ_FIBER: printf("[fiber %p]", obj); break; - case OBJ_FN: printf("[fn %p]", obj); break; - case OBJ_FOREIGN: printf("[foreign %p]", obj); break; - case OBJ_INSTANCE: printf("[instance %p]", obj); break; - case OBJ_LIST: printf("[list %p]", obj); break; - case OBJ_MAP: printf("[map %p]", obj); break; - case OBJ_MODULE: printf("[module %p]", obj); break; - case OBJ_RANGE: printf("[range %p]", obj); break; - case OBJ_STRING: printf("%s", ((ObjString*)obj)->value); break; - case OBJ_UPVALUE: printf("[upvalue %p]", obj); break; - default: printf("[unknown object %d]", obj->type); break; - } -} - -void wrenDumpValue(Value value) -{ -#if WREN_NAN_TAGGING - if (IS_NUM(value)) - { - printf("%.14g", AS_NUM(value)); - } - else if (IS_OBJ(value)) - { - dumpObject(AS_OBJ(value)); - } - else - { - switch (GET_TAG(value)) - { - case TAG_FALSE: printf("false"); break; - case TAG_NAN: printf("NaN"); break; - case TAG_NULL: printf("null"); break; - case TAG_TRUE: printf("true"); break; - case TAG_UNDEFINED: UNREACHABLE(); - } - } -#else - switch (value.type) - { - case VAL_FALSE: printf("false"); break; - case VAL_NULL: printf("null"); break; - case VAL_NUM: printf("%.14g", AS_NUM(value)); break; - case VAL_TRUE: printf("true"); break; - case VAL_OBJ: dumpObject(AS_OBJ(value)); break; - case VAL_UNDEFINED: UNREACHABLE(); - } -#endif -} - -static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine) -{ - int start = i; - uint8_t* bytecode = fn->code.data; - Code code = (Code)bytecode[i]; - - int line = fn->debug->sourceLines.data[i]; - if (lastLine == NULL || *lastLine != line) - { - printf("%4d:", line); - if (lastLine != NULL) *lastLine = line; - } - else - { - printf(" "); - } - - printf(" %04d ", i++); - - #define READ_BYTE() (bytecode[i++]) - #define READ_SHORT() (i += 2, (bytecode[i - 2] << 8) | bytecode[i - 1]) - - #define BYTE_INSTRUCTION(name) \ - printf("%-16s %5d\n", name, READ_BYTE()); \ - break - - switch (code) - { - case CODE_CONSTANT: - { - int constant = READ_SHORT(); - printf("%-16s %5d '", "CONSTANT", constant); - wrenDumpValue(fn->constants.data[constant]); - printf("'\n"); - break; - } - - case CODE_NULL: printf("NULL\n"); break; - case CODE_FALSE: printf("FALSE\n"); break; - case CODE_TRUE: printf("TRUE\n"); break; - - case CODE_LOAD_LOCAL_0: printf("LOAD_LOCAL_0\n"); break; - case CODE_LOAD_LOCAL_1: printf("LOAD_LOCAL_1\n"); break; - case CODE_LOAD_LOCAL_2: printf("LOAD_LOCAL_2\n"); break; - case CODE_LOAD_LOCAL_3: printf("LOAD_LOCAL_3\n"); break; - case CODE_LOAD_LOCAL_4: printf("LOAD_LOCAL_4\n"); break; - case CODE_LOAD_LOCAL_5: printf("LOAD_LOCAL_5\n"); break; - case CODE_LOAD_LOCAL_6: printf("LOAD_LOCAL_6\n"); break; - case CODE_LOAD_LOCAL_7: printf("LOAD_LOCAL_7\n"); break; - case CODE_LOAD_LOCAL_8: printf("LOAD_LOCAL_8\n"); break; - - case CODE_LOAD_LOCAL: BYTE_INSTRUCTION("LOAD_LOCAL"); - case CODE_STORE_LOCAL: BYTE_INSTRUCTION("STORE_LOCAL"); - case CODE_LOAD_UPVALUE: BYTE_INSTRUCTION("LOAD_UPVALUE"); - case CODE_STORE_UPVALUE: BYTE_INSTRUCTION("STORE_UPVALUE"); - - case CODE_LOAD_MODULE_VAR: - { - int slot = READ_SHORT(); - printf("%-16s %5d '%s'\n", "LOAD_MODULE_VAR", slot, - fn->module->variableNames.data[slot]->value); - break; - } - - case CODE_STORE_MODULE_VAR: - { - int slot = READ_SHORT(); - printf("%-16s %5d '%s'\n", "STORE_MODULE_VAR", slot, - fn->module->variableNames.data[slot]->value); - break; - } - - case CODE_LOAD_FIELD_THIS: BYTE_INSTRUCTION("LOAD_FIELD_THIS"); - case CODE_STORE_FIELD_THIS: BYTE_INSTRUCTION("STORE_FIELD_THIS"); - case CODE_LOAD_FIELD: BYTE_INSTRUCTION("LOAD_FIELD"); - case CODE_STORE_FIELD: BYTE_INSTRUCTION("STORE_FIELD"); - - case CODE_POP: printf("POP\n"); break; - - case CODE_CALL_0: - case CODE_CALL_1: - case CODE_CALL_2: - case CODE_CALL_3: - case CODE_CALL_4: - case CODE_CALL_5: - case CODE_CALL_6: - case CODE_CALL_7: - case CODE_CALL_8: - case CODE_CALL_9: - case CODE_CALL_10: - case CODE_CALL_11: - case CODE_CALL_12: - case CODE_CALL_13: - case CODE_CALL_14: - case CODE_CALL_15: - case CODE_CALL_16: - { - int numArgs = bytecode[i - 1] - CODE_CALL_0; - int symbol = READ_SHORT(); - printf("CALL_%-11d %5d '%s'\n", numArgs, symbol, - vm->methodNames.data[symbol]->value); - break; - } - - case CODE_SUPER_0: - case CODE_SUPER_1: - case CODE_SUPER_2: - case CODE_SUPER_3: - case CODE_SUPER_4: - case CODE_SUPER_5: - case CODE_SUPER_6: - case CODE_SUPER_7: - case CODE_SUPER_8: - case CODE_SUPER_9: - case CODE_SUPER_10: - case CODE_SUPER_11: - case CODE_SUPER_12: - case CODE_SUPER_13: - case CODE_SUPER_14: - case CODE_SUPER_15: - case CODE_SUPER_16: - { - int numArgs = bytecode[i - 1] - CODE_SUPER_0; - int symbol = READ_SHORT(); - int superclass = READ_SHORT(); - printf("SUPER_%-10d %5d '%s' %5d\n", numArgs, symbol, - vm->methodNames.data[symbol]->value, superclass); - break; - } - - case CODE_JUMP: - { - int offset = READ_SHORT(); - printf("%-16s %5d to %d\n", "JUMP", offset, i + offset); - break; - } - - case CODE_LOOP: - { - int offset = READ_SHORT(); - printf("%-16s %5d to %d\n", "LOOP", offset, i - offset); - break; - } - - case CODE_JUMP_IF: - { - int offset = READ_SHORT(); - printf("%-16s %5d to %d\n", "JUMP_IF", offset, i + offset); - break; - } - - case CODE_AND: - { - int offset = READ_SHORT(); - printf("%-16s %5d to %d\n", "AND", offset, i + offset); - break; - } - - case CODE_OR: - { - int offset = READ_SHORT(); - printf("%-16s %5d to %d\n", "OR", offset, i + offset); - break; - } - - case CODE_CLOSE_UPVALUE: printf("CLOSE_UPVALUE\n"); break; - case CODE_RETURN: printf("RETURN\n"); break; - - case CODE_CLOSURE: - { - int constant = READ_SHORT(); - printf("%-16s %5d ", "CLOSURE", constant); - wrenDumpValue(fn->constants.data[constant]); - printf(" "); - ObjFn* loadedFn = AS_FN(fn->constants.data[constant]); - for (int j = 0; j < loadedFn->numUpvalues; j++) - { - int isLocal = READ_BYTE(); - int index = READ_BYTE(); - if (j > 0) printf(", "); - printf("%s %d", isLocal ? "local" : "upvalue", index); - } - printf("\n"); - break; - } - - case CODE_CONSTRUCT: printf("CONSTRUCT\n"); break; - case CODE_FOREIGN_CONSTRUCT: printf("FOREIGN_CONSTRUCT\n"); break; - - case CODE_CLASS: - { - int numFields = READ_BYTE(); - printf("%-16s %5d fields\n", "CLASS", numFields); - break; - } - - case CODE_FOREIGN_CLASS: printf("FOREIGN_CLASS\n"); break; - case CODE_END_CLASS: printf("END_CLASS\n"); break; - - case CODE_METHOD_INSTANCE: - { - int symbol = READ_SHORT(); - printf("%-16s %5d '%s'\n", "METHOD_INSTANCE", symbol, - vm->methodNames.data[symbol]->value); - break; - } - - case CODE_METHOD_STATIC: - { - int symbol = READ_SHORT(); - printf("%-16s %5d '%s'\n", "METHOD_STATIC", symbol, - vm->methodNames.data[symbol]->value); - break; - } - - case CODE_END_MODULE: - printf("END_MODULE\n"); - break; - - case CODE_IMPORT_MODULE: - { - int name = READ_SHORT(); - printf("%-16s %5d '", "IMPORT_MODULE", name); - wrenDumpValue(fn->constants.data[name]); - printf("'\n"); - break; - } - - case CODE_IMPORT_VARIABLE: - { - int variable = READ_SHORT(); - printf("%-16s %5d '", "IMPORT_VARIABLE", variable); - wrenDumpValue(fn->constants.data[variable]); - printf("'\n"); - break; - } - - case CODE_END: - printf("END\n"); - break; - - default: - printf("UKNOWN! [%d]\n", bytecode[i - 1]); - break; - } - - // Return how many bytes this instruction takes, or -1 if it's an END. - if (code == CODE_END) return -1; - return i - start; - - #undef READ_BYTE - #undef READ_SHORT -} - -int wrenDumpInstruction(WrenVM* vm, ObjFn* fn, int i) -{ - return dumpInstruction(vm, fn, i, NULL); -} - -void wrenDumpCode(WrenVM* vm, ObjFn* fn) -{ - printf("%s: %s\n", - fn->module->name == NULL ? "" : fn->module->name->value, - fn->debug->name); - - int i = 0; - int lastLine = -1; - for (;;) - { - int offset = dumpInstruction(vm, fn, i, &lastLine); - if (offset == -1) break; - i += offset; - } - - printf("\n"); -} - -void wrenDumpStack(ObjFiber* fiber) -{ - printf("(fiber %p) ", fiber); - for (Value* slot = fiber->stack; slot < fiber->stackTop; slot++) - { - wrenDumpValue(*slot); - printf(" | "); - } - printf("\n"); -} -// End file "wren_debug.c" -// Begin file "wren_compiler.c" -#include -#include -#include -#include - - -#if WREN_DEBUG_DUMP_COMPILED_CODE -#endif - -// This is written in bottom-up order, so the tokenization comes first, then -// parsing/code generation. This minimizes the number of explicit forward -// declarations needed. - -// The maximum number of local (i.e. not module level) variables that can be -// declared in a single function, method, or chunk of top level code. This is -// the maximum number of variables in scope at one time, and spans block scopes. -// -// Note that this limitation is also explicit in the bytecode. Since -// `CODE_LOAD_LOCAL` and `CODE_STORE_LOCAL` use a single argument byte to -// identify the local, only 256 can be in scope at one time. -#define MAX_LOCALS 256 - -// The maximum number of upvalues (i.e. variables from enclosing functions) -// that a function can close over. -#define MAX_UPVALUES 256 - -// The maximum number of distinct constants that a function can contain. This -// value is explicit in the bytecode since `CODE_CONSTANT` only takes a single -// two-byte argument. -#define MAX_CONSTANTS (1 << 16) - -// The maximum distance a CODE_JUMP or CODE_JUMP_IF instruction can move the -// instruction pointer. -#define MAX_JUMP (1 << 16) - -// The maximum depth that interpolation can nest. For example, this string has -// three levels: -// -// "outside %(one + "%(two + "%(three)")")" -#define MAX_INTERPOLATION_NESTING 8 - -// The buffer size used to format a compile error message, excluding the header -// with the module name and error location. Using a hardcoded buffer for this -// is kind of hairy, but fortunately we can control what the longest possible -// message is and handle that. Ideally, we'd use `snprintf()`, but that's not -// available in standard C++98. -#define ERROR_MESSAGE_SIZE (80 + MAX_VARIABLE_NAME + 15) - -typedef enum -{ - TOKEN_LEFT_PAREN, - TOKEN_RIGHT_PAREN, - TOKEN_LEFT_BRACKET, - TOKEN_RIGHT_BRACKET, - TOKEN_LEFT_BRACE, - TOKEN_RIGHT_BRACE, - TOKEN_COLON, - TOKEN_DOT, - TOKEN_DOTDOT, - TOKEN_DOTDOTDOT, - TOKEN_COMMA, - TOKEN_STAR, - TOKEN_SLASH, - TOKEN_PERCENT, - TOKEN_HASH, - TOKEN_PLUS, - TOKEN_MINUS, - TOKEN_LTLT, - TOKEN_GTGT, - TOKEN_PIPE, - TOKEN_PIPEPIPE, - TOKEN_CARET, - TOKEN_AMP, - TOKEN_AMPAMP, - TOKEN_BANG, - TOKEN_TILDE, - TOKEN_QUESTION, - TOKEN_EQ, - TOKEN_LT, - TOKEN_GT, - TOKEN_LTEQ, - TOKEN_GTEQ, - TOKEN_EQEQ, - TOKEN_BANGEQ, - - TOKEN_BREAK, - TOKEN_CONTINUE, - TOKEN_CLASS, - TOKEN_CONSTRUCT, - TOKEN_ELSE, - TOKEN_FALSE, - TOKEN_FOR, - TOKEN_FOREIGN, - TOKEN_IF, - TOKEN_IMPORT, - TOKEN_AS, - TOKEN_IN, - TOKEN_IS, - TOKEN_NULL, - TOKEN_RETURN, - TOKEN_STATIC, - TOKEN_SUPER, - TOKEN_THIS, - TOKEN_TRUE, - TOKEN_VAR, - TOKEN_WHILE, - - TOKEN_FIELD, - TOKEN_STATIC_FIELD, - TOKEN_NAME, - TOKEN_NUMBER, - - // A string literal without any interpolation, or the last section of a - // string following the last interpolated expression. - TOKEN_STRING, - - // A portion of a string literal preceding an interpolated expression. This - // string: - // - // "a %(b) c %(d) e" - // - // is tokenized to: - // - // TOKEN_INTERPOLATION "a " - // TOKEN_NAME b - // TOKEN_INTERPOLATION " c " - // TOKEN_NAME d - // TOKEN_STRING " e" - TOKEN_INTERPOLATION, - - TOKEN_LINE, - - TOKEN_ERROR, - TOKEN_EOF -} TokenType; - -typedef struct -{ - TokenType type; - - // The beginning of the token, pointing directly into the source. - const char* start; - - // The length of the token in characters. - int length; - - // The 1-based line where the token appears. - int line; - - // The parsed value if the token is a literal. - Value value; -} Token; - -typedef struct -{ - WrenVM* vm; - - // The module being parsed. - ObjModule* module; - - // The source code being parsed. - const char* source; - - // The beginning of the currently-being-lexed token in [source]. - const char* tokenStart; - - // The current character being lexed in [source]. - const char* currentChar; - - // The 1-based line number of [currentChar]. - int currentLine; - - // The upcoming token. - Token next; - - // The most recently lexed token. - Token current; - - // The most recently consumed/advanced token. - Token previous; - - // Tracks the lexing state when tokenizing interpolated strings. - // - // Interpolated strings make the lexer not strictly regular: we don't know - // whether a ")" should be treated as a RIGHT_PAREN token or as ending an - // interpolated expression unless we know whether we are inside a string - // interpolation and how many unmatched "(" there are. This is particularly - // complex because interpolation can nest: - // - // " %( " %( inner ) " ) " - // - // This tracks that state. The parser maintains a stack of ints, one for each - // level of current interpolation nesting. Each value is the number of - // unmatched "(" that are waiting to be closed. - int parens[MAX_INTERPOLATION_NESTING]; - int numParens; - - // Whether compile errors should be printed to stderr or discarded. - bool printErrors; - - // If a syntax or compile error has occurred. - bool hasError; -} Parser; - -typedef struct -{ - // The name of the local variable. This points directly into the original - // source code string. - const char* name; - - // The length of the local variable's name. - int length; - - // The depth in the scope chain that this variable was declared at. Zero is - // the outermost scope--parameters for a method, or the first local block in - // top level code. One is the scope within that, etc. - int depth; - - // If this local variable is being used as an upvalue. - bool isUpvalue; -} Local; - -typedef struct -{ - // True if this upvalue is capturing a local variable from the enclosing - // function. False if it's capturing an upvalue. - bool isLocal; - - // The index of the local or upvalue being captured in the enclosing function. - int index; -} CompilerUpvalue; - -// Bookkeeping information for the current loop being compiled. -typedef struct sLoop -{ - // Index of the instruction that the loop should jump back to. - int start; - - // Index of the argument for the CODE_JUMP_IF instruction used to exit the - // loop. Stored so we can patch it once we know where the loop ends. - int exitJump; - - // Index of the first instruction of the body of the loop. - int body; - - // Depth of the scope(s) that need to be exited if a break is hit inside the - // loop. - int scopeDepth; - - // The loop enclosing this one, or NULL if this is the outermost loop. - struct sLoop* enclosing; -} Loop; - -// The different signature syntaxes for different kinds of methods. -typedef enum -{ - // A name followed by a (possibly empty) parenthesized parameter list. Also - // used for binary operators. - SIG_METHOD, - - // Just a name. Also used for unary operators. - SIG_GETTER, - - // A name followed by "=". - SIG_SETTER, - - // A square bracketed parameter list. - SIG_SUBSCRIPT, - - // A square bracketed parameter list followed by "=". - SIG_SUBSCRIPT_SETTER, - - // A constructor initializer function. This has a distinct signature to - // prevent it from being invoked directly outside of the constructor on the - // metaclass. - SIG_INITIALIZER -} SignatureType; - -typedef struct -{ - const char* name; - int length; - SignatureType type; - int arity; -} Signature; - -// Bookkeeping information for compiling a class definition. -typedef struct -{ - // The name of the class. - ObjString* name; - - // Attributes for the class itself - ObjMap* classAttributes; - // Attributes for methods in this class - ObjMap* methodAttributes; - - // Symbol table for the fields of the class. - SymbolTable fields; - - // Symbols for the methods defined by the class. Used to detect duplicate - // method definitions. - IntBuffer methods; - IntBuffer staticMethods; - - // True if the class being compiled is a foreign class. - bool isForeign; - - // True if the current method being compiled is static. - bool inStatic; - - // The signature of the method being compiled. - Signature* signature; -} ClassInfo; - -struct sCompiler -{ - Parser* parser; - - // The compiler for the function enclosing this one, or NULL if it's the - // top level. - struct sCompiler* parent; - - // The currently in scope local variables. - Local locals[MAX_LOCALS]; - - // The number of local variables currently in scope. - int numLocals; - - // The upvalues that this function has captured from outer scopes. The count - // of them is stored in [numUpvalues]. - CompilerUpvalue upvalues[MAX_UPVALUES]; - - // The current level of block scope nesting, where zero is no nesting. A -1 - // here means top-level code is being compiled and there is no block scope - // in effect at all. Any variables declared will be module-level. - int scopeDepth; - - // The current number of slots (locals and temporaries) in use. - // - // We use this and maxSlots to track the maximum number of additional slots - // a function may need while executing. When the function is called, the - // fiber will check to ensure its stack has enough room to cover that worst - // case and grow the stack if needed. - // - // This value here doesn't include parameters to the function. Since those - // are already pushed onto the stack by the caller and tracked there, we - // don't need to double count them here. - int numSlots; - - // The current innermost loop being compiled, or NULL if not in a loop. - Loop* loop; - - // If this is a compiler for a method, keeps track of the class enclosing it. - ClassInfo* enclosingClass; - - // The function being compiled. - ObjFn* fn; - - // The constants for the function being compiled. - ObjMap* constants; - - // Whether or not the compiler is for a constructor initializer - bool isInitializer; - - // The number of attributes seen while parsing. - // We track this separately as compile time attributes - // are not stored, so we can't rely on attributes->count - // to enforce an error message when attributes are used - // anywhere other than methods or classes. - int numAttributes; - // Attributes for the next class or method. - ObjMap* attributes; -}; - -// Describes where a variable is declared. -typedef enum -{ - // A local variable in the current function. - SCOPE_LOCAL, - - // A local variable declared in an enclosing function. - SCOPE_UPVALUE, - - // A top-level module variable. - SCOPE_MODULE -} Scope; - -// A reference to a variable and the scope where it is defined. This contains -// enough information to emit correct code to load or store the variable. -typedef struct -{ - // The stack slot, upvalue slot, or module symbol defining the variable. - int index; - - // Where the variable is declared. - Scope scope; -} Variable; - -// Forward declarations -static void disallowAttributes(Compiler* compiler); -static void addToAttributeGroup(Compiler* compiler, Value group, Value key, Value value); -static void emitClassAttributes(Compiler* compiler, ClassInfo* classInfo); -static void copyAttributes(Compiler* compiler, ObjMap* into); -static void copyMethodAttributes(Compiler* compiler, bool isForeign, - bool isStatic, const char* fullSignature, int32_t length); - -// The stack effect of each opcode. The index in the array is the opcode, and -// the value is the stack effect of that instruction. -static const int stackEffects[] = { - #define OPCODE(_, effect) effect, -// Begin file "wren_opcodes.h" -// This defines the bytecode instructions used by the VM. It does so by invoking -// an OPCODE() macro which is expected to be defined at the point that this is -// included. (See: http://en.wikipedia.org/wiki/X_Macro for more.) -// -// The first argument is the name of the opcode. The second is its "stack -// effect" -- the amount that the op code changes the size of the stack. A -// stack effect of 1 means it pushes a value and the stack grows one larger. -// -2 means it pops two values, etc. -// -// Note that the order of instructions here affects the order of the dispatch -// table in the VM's interpreter loop. That in turn affects caching which -// affects overall performance. Take care to run benchmarks if you change the -// order here. - -// Load the constant at index [arg]. -OPCODE(CONSTANT, 1) - -// Push null onto the stack. -OPCODE(NULL, 1) - -// Push false onto the stack. -OPCODE(FALSE, 1) - -// Push true onto the stack. -OPCODE(TRUE, 1) - -// Pushes the value in the given local slot. -OPCODE(LOAD_LOCAL_0, 1) -OPCODE(LOAD_LOCAL_1, 1) -OPCODE(LOAD_LOCAL_2, 1) -OPCODE(LOAD_LOCAL_3, 1) -OPCODE(LOAD_LOCAL_4, 1) -OPCODE(LOAD_LOCAL_5, 1) -OPCODE(LOAD_LOCAL_6, 1) -OPCODE(LOAD_LOCAL_7, 1) -OPCODE(LOAD_LOCAL_8, 1) - -// Note: The compiler assumes the following _STORE instructions always -// immediately follow their corresponding _LOAD ones. - -// Pushes the value in local slot [arg]. -OPCODE(LOAD_LOCAL, 1) - -// Stores the top of stack in local slot [arg]. Does not pop it. -OPCODE(STORE_LOCAL, 0) - -// Pushes the value in upvalue [arg]. -OPCODE(LOAD_UPVALUE, 1) - -// Stores the top of stack in upvalue [arg]. Does not pop it. -OPCODE(STORE_UPVALUE, 0) - -// Pushes the value of the top-level variable in slot [arg]. -OPCODE(LOAD_MODULE_VAR, 1) - -// Stores the top of stack in top-level variable slot [arg]. Does not pop it. -OPCODE(STORE_MODULE_VAR, 0) - -// Pushes the value of the field in slot [arg] of the receiver of the current -// function. This is used for regular field accesses on "this" directly in -// methods. This instruction is faster than the more general CODE_LOAD_FIELD -// instruction. -OPCODE(LOAD_FIELD_THIS, 1) - -// Stores the top of the stack in field slot [arg] in the receiver of the -// current value. Does not pop the value. This instruction is faster than the -// more general CODE_LOAD_FIELD instruction. -OPCODE(STORE_FIELD_THIS, 0) - -// Pops an instance and pushes the value of the field in slot [arg] of it. -OPCODE(LOAD_FIELD, 0) - -// Pops an instance and stores the subsequent top of stack in field slot -// [arg] in it. Does not pop the value. -OPCODE(STORE_FIELD, -1) - -// Pop and discard the top of stack. -OPCODE(POP, -1) - -// Invoke the method with symbol [arg]. The number indicates the number of -// arguments (not including the receiver). -OPCODE(CALL_0, 0) -OPCODE(CALL_1, -1) -OPCODE(CALL_2, -2) -OPCODE(CALL_3, -3) -OPCODE(CALL_4, -4) -OPCODE(CALL_5, -5) -OPCODE(CALL_6, -6) -OPCODE(CALL_7, -7) -OPCODE(CALL_8, -8) -OPCODE(CALL_9, -9) -OPCODE(CALL_10, -10) -OPCODE(CALL_11, -11) -OPCODE(CALL_12, -12) -OPCODE(CALL_13, -13) -OPCODE(CALL_14, -14) -OPCODE(CALL_15, -15) -OPCODE(CALL_16, -16) - -// Invoke a superclass method with symbol [arg]. The number indicates the -// number of arguments (not including the receiver). -OPCODE(SUPER_0, 0) -OPCODE(SUPER_1, -1) -OPCODE(SUPER_2, -2) -OPCODE(SUPER_3, -3) -OPCODE(SUPER_4, -4) -OPCODE(SUPER_5, -5) -OPCODE(SUPER_6, -6) -OPCODE(SUPER_7, -7) -OPCODE(SUPER_8, -8) -OPCODE(SUPER_9, -9) -OPCODE(SUPER_10, -10) -OPCODE(SUPER_11, -11) -OPCODE(SUPER_12, -12) -OPCODE(SUPER_13, -13) -OPCODE(SUPER_14, -14) -OPCODE(SUPER_15, -15) -OPCODE(SUPER_16, -16) - -// Jump the instruction pointer [arg] forward. -OPCODE(JUMP, 0) - -// Jump the instruction pointer [arg] backward. -OPCODE(LOOP, 0) - -// Pop and if not truthy then jump the instruction pointer [arg] forward. -OPCODE(JUMP_IF, -1) - -// If the top of the stack is false, jump [arg] forward. Otherwise, pop and -// continue. -OPCODE(AND, -1) - -// If the top of the stack is non-false, jump [arg] forward. Otherwise, pop -// and continue. -OPCODE(OR, -1) - -// Close the upvalue for the local on the top of the stack, then pop it. -OPCODE(CLOSE_UPVALUE, -1) - -// Exit from the current function and return the value on the top of the -// stack. -OPCODE(RETURN, 0) - -// Creates a closure for the function stored at [arg] in the constant table. -// -// Following the function argument is a number of arguments, two for each -// upvalue. The first is true if the variable being captured is a local (as -// opposed to an upvalue), and the second is the index of the local or -// upvalue being captured. -// -// Pushes the created closure. -OPCODE(CLOSURE, 1) - -// Creates a new instance of a class. -// -// Assumes the class object is in slot zero, and replaces it with the new -// uninitialized instance of that class. This opcode is only emitted by the -// compiler-generated constructor metaclass methods. -OPCODE(CONSTRUCT, 0) - -// Creates a new instance of a foreign class. -// -// Assumes the class object is in slot zero, and replaces it with the new -// uninitialized instance of that class. This opcode is only emitted by the -// compiler-generated constructor metaclass methods. -OPCODE(FOREIGN_CONSTRUCT, 0) - -// Creates a class. Top of stack is the superclass. Below that is a string for -// the name of the class. Byte [arg] is the number of fields in the class. -OPCODE(CLASS, -1) - -// Ends a class. -// Atm the stack contains the class and the ClassAttributes (or null). -OPCODE(END_CLASS, -2) - -// Creates a foreign class. Top of stack is the superclass. Below that is a -// string for the name of the class. -OPCODE(FOREIGN_CLASS, -1) - -// Define a method for symbol [arg]. The class receiving the method is popped -// off the stack, then the function defining the body is popped. -// -// If a foreign method is being defined, the "function" will be a string -// identifying the foreign method. Otherwise, it will be a function or -// closure. -OPCODE(METHOD_INSTANCE, -2) - -// Define a method for symbol [arg]. The class whose metaclass will receive -// the method is popped off the stack, then the function defining the body is -// popped. -// -// If a foreign method is being defined, the "function" will be a string -// identifying the foreign method. Otherwise, it will be a function or -// closure. -OPCODE(METHOD_STATIC, -2) - -// This is executed at the end of the module's body. Pushes NULL onto the stack -// as the "return value" of the import statement and stores the module as the -// most recently imported one. -OPCODE(END_MODULE, 1) - -// Import a module whose name is the string stored at [arg] in the constant -// table. -// -// Pushes null onto the stack so that the fiber for the imported module can -// replace that with a dummy value when it returns. (Fibers always return a -// value when resuming a caller.) -OPCODE(IMPORT_MODULE, 1) - -// Import a variable from the most recently imported module. The name of the -// variable to import is at [arg] in the constant table. Pushes the loaded -// variable's value. -OPCODE(IMPORT_VARIABLE, 1) - -// This pseudo-instruction indicates the end of the bytecode. It should -// always be preceded by a `CODE_RETURN`, so is never actually executed. -OPCODE(END, 0) -// End file "wren_opcodes.h" - #undef OPCODE -}; - -static void printError(Parser* parser, int line, const char* label, - const char* format, va_list args) -{ - parser->hasError = true; - if (!parser->printErrors) return; - - // Only report errors if there is a WrenErrorFn to handle them. - if (parser->vm->config.errorFn == NULL) return; - - // Format the label and message. - char message[ERROR_MESSAGE_SIZE]; - int length = sprintf(message, "%s: ", label); - length += vsprintf(message + length, format, args); - ASSERT(length < ERROR_MESSAGE_SIZE, "Error should not exceed buffer."); - - ObjString* module = parser->module->name; - const char* module_name = module ? module->value : ""; - - parser->vm->config.errorFn(parser->vm, WREN_ERROR_COMPILE, - module_name, line, message); -} - -// Outputs a lexical error. -static void lexError(Parser* parser, const char* format, ...) -{ - va_list args; - va_start(args, format); - printError(parser, parser->currentLine, "Error", format, args); - va_end(args); -} - -// Outputs a compile or syntax error. This also marks the compilation as having -// an error, which ensures that the resulting code will be discarded and never -// run. This means that after calling error(), it's fine to generate whatever -// invalid bytecode you want since it won't be used. -// -// You'll note that most places that call error() continue to parse and compile -// after that. That's so that we can try to find as many compilation errors in -// one pass as possible instead of just bailing at the first one. -static void error(Compiler* compiler, const char* format, ...) -{ - Token* token = &compiler->parser->previous; - - // If the parse error was caused by an error token, the lexer has already - // reported it. - if (token->type == TOKEN_ERROR) return; - - va_list args; - va_start(args, format); - if (token->type == TOKEN_LINE) - { - printError(compiler->parser, token->line, "Error at newline", format, args); - } - else if (token->type == TOKEN_EOF) - { - printError(compiler->parser, token->line, - "Error at end of file", format, args); - } - else - { - // Make sure we don't exceed the buffer with a very long token. - char label[10 + MAX_VARIABLE_NAME + 4 + 1]; - if (token->length <= MAX_VARIABLE_NAME) - { - sprintf(label, "Error at '%.*s'", token->length, token->start); - } - else - { - sprintf(label, "Error at '%.*s...'", MAX_VARIABLE_NAME, token->start); - } - printError(compiler->parser, token->line, label, format, args); - } - va_end(args); -} - -// Adds [constant] to the constant pool and returns its index. -static int addConstant(Compiler* compiler, Value constant) -{ - if (compiler->parser->hasError) return -1; - - // See if we already have a constant for the value. If so, reuse it. - if (compiler->constants != NULL) - { - Value existing = wrenMapGet(compiler->constants, constant); - if (IS_NUM(existing)) return (int)AS_NUM(existing); - } - - // It's a new constant. - if (compiler->fn->constants.count < MAX_CONSTANTS) - { - if (IS_OBJ(constant)) wrenPushRoot(compiler->parser->vm, AS_OBJ(constant)); - wrenValueBufferWrite(compiler->parser->vm, &compiler->fn->constants, - constant); - if (IS_OBJ(constant)) wrenPopRoot(compiler->parser->vm); - - if (compiler->constants == NULL) - { - compiler->constants = wrenNewMap(compiler->parser->vm); - } - wrenMapSet(compiler->parser->vm, compiler->constants, constant, - NUM_VAL(compiler->fn->constants.count - 1)); - } - else - { - error(compiler, "A function may only contain %d unique constants.", - MAX_CONSTANTS); - } - - return compiler->fn->constants.count - 1; -} - -// Initializes [compiler]. -static void initCompiler(Compiler* compiler, Parser* parser, Compiler* parent, - bool isMethod) -{ - compiler->parser = parser; - compiler->parent = parent; - compiler->loop = NULL; - compiler->enclosingClass = NULL; - compiler->isInitializer = false; - - // Initialize these to NULL before allocating in case a GC gets triggered in - // the middle of initializing the compiler. - compiler->fn = NULL; - compiler->constants = NULL; - compiler->attributes = NULL; - - parser->vm->compiler = compiler; - - // Declare a local slot for either the closure or method receiver so that we - // don't try to reuse that slot for a user-defined local variable. For - // methods, we name it "this", so that we can resolve references to that like - // a normal variable. For functions, they have no explicit "this", so we use - // an empty name. That way references to "this" inside a function walks up - // the parent chain to find a method enclosing the function whose "this" we - // can close over. - compiler->numLocals = 1; - compiler->numSlots = compiler->numLocals; - - if (isMethod) - { - compiler->locals[0].name = "this"; - compiler->locals[0].length = 4; - } - else - { - compiler->locals[0].name = NULL; - compiler->locals[0].length = 0; - } - - compiler->locals[0].depth = -1; - compiler->locals[0].isUpvalue = false; - - if (parent == NULL) - { - // Compiling top-level code, so the initial scope is module-level. - compiler->scopeDepth = -1; - } - else - { - // The initial scope for functions and methods is local scope. - compiler->scopeDepth = 0; - } - - compiler->numAttributes = 0; - compiler->attributes = wrenNewMap(parser->vm); - compiler->fn = wrenNewFunction(parser->vm, parser->module, - compiler->numLocals); -} - -// Lexing ---------------------------------------------------------------------- - -typedef struct -{ - const char* identifier; - size_t length; - TokenType tokenType; -} Keyword; - -// The table of reserved words and their associated token types. -static Keyword keywords[] = -{ - {"break", 5, TOKEN_BREAK}, - {"continue", 8, TOKEN_CONTINUE}, - {"class", 5, TOKEN_CLASS}, - {"construct", 9, TOKEN_CONSTRUCT}, - {"else", 4, TOKEN_ELSE}, - {"false", 5, TOKEN_FALSE}, - {"for", 3, TOKEN_FOR}, - {"foreign", 7, TOKEN_FOREIGN}, - {"if", 2, TOKEN_IF}, - {"import", 6, TOKEN_IMPORT}, - {"as", 2, TOKEN_AS}, - {"in", 2, TOKEN_IN}, - {"is", 2, TOKEN_IS}, - {"null", 4, TOKEN_NULL}, - {"return", 6, TOKEN_RETURN}, - {"static", 6, TOKEN_STATIC}, - {"super", 5, TOKEN_SUPER}, - {"this", 4, TOKEN_THIS}, - {"true", 4, TOKEN_TRUE}, - {"var", 3, TOKEN_VAR}, - {"while", 5, TOKEN_WHILE}, - {NULL, 0, TOKEN_EOF} // Sentinel to mark the end of the array. -}; - -// Returns true if [c] is a valid (non-initial) identifier character. -static bool isName(char c) -{ - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; -} - -// Returns true if [c] is a digit. -static bool isDigit(char c) -{ - return c >= '0' && c <= '9'; -} - -// Returns the current character the parser is sitting on. -static char peekChar(Parser* parser) -{ - return *parser->currentChar; -} - -// Returns the character after the current character. -static char peekNextChar(Parser* parser) -{ - // If we're at the end of the source, don't read past it. - if (peekChar(parser) == '\0') return '\0'; - return *(parser->currentChar + 1); -} - -// Advances the parser forward one character. -static char nextChar(Parser* parser) -{ - char c = peekChar(parser); - parser->currentChar++; - if (c == '\n') parser->currentLine++; - return c; -} - -// If the current character is [c], consumes it and returns `true`. -static bool matchChar(Parser* parser, char c) -{ - if (peekChar(parser) != c) return false; - nextChar(parser); - return true; -} - -// Sets the parser's current token to the given [type] and current character -// range. -static void makeToken(Parser* parser, TokenType type) -{ - parser->next.type = type; - parser->next.start = parser->tokenStart; - parser->next.length = (int)(parser->currentChar - parser->tokenStart); - parser->next.line = parser->currentLine; - - // Make line tokens appear on the line containing the "\n". - if (type == TOKEN_LINE) parser->next.line--; -} - -// If the current character is [c], then consumes it and makes a token of type -// [two]. Otherwise makes a token of type [one]. -static void twoCharToken(Parser* parser, char c, TokenType two, TokenType one) -{ - makeToken(parser, matchChar(parser, c) ? two : one); -} - -// Skips the rest of the current line. -static void skipLineComment(Parser* parser) -{ - while (peekChar(parser) != '\n' && peekChar(parser) != '\0') - { - nextChar(parser); - } -} - -// Skips the rest of a block comment. -static void skipBlockComment(Parser* parser) -{ - int nesting = 1; - while (nesting > 0) - { - if (peekChar(parser) == '\0') - { - lexError(parser, "Unterminated block comment."); - return; - } - - if (peekChar(parser) == '/' && peekNextChar(parser) == '*') - { - nextChar(parser); - nextChar(parser); - nesting++; - continue; - } - - if (peekChar(parser) == '*' && peekNextChar(parser) == '/') - { - nextChar(parser); - nextChar(parser); - nesting--; - continue; - } - - // Regular comment character. - nextChar(parser); - } -} - -// Reads the next character, which should be a hex digit (0-9, a-f, or A-F) and -// returns its numeric value. If the character isn't a hex digit, returns -1. -static int readHexDigit(Parser* parser) -{ - char c = nextChar(parser); - if (c >= '0' && c <= '9') return c - '0'; - if (c >= 'a' && c <= 'f') return c - 'a' + 10; - if (c >= 'A' && c <= 'F') return c - 'A' + 10; - - // Don't consume it if it isn't expected. Keeps us from reading past the end - // of an unterminated string. - parser->currentChar--; - return -1; -} - -// Parses the numeric value of the current token. -static void makeNumber(Parser* parser, bool isHex) -{ - errno = 0; - - if (isHex) - { - parser->next.value = NUM_VAL((double)strtoll(parser->tokenStart, NULL, 16)); - } - else - { - parser->next.value = NUM_VAL(strtod(parser->tokenStart, NULL)); - } - - if (errno == ERANGE) - { - lexError(parser, "Number literal was too large (%d).", sizeof(long int)); - parser->next.value = NUM_VAL(0); - } - - // We don't check that the entire token is consumed after calling strtoll() - // or strtod() because we've already scanned it ourselves and know it's valid. - - makeToken(parser, TOKEN_NUMBER); -} - -// Finishes lexing a hexadecimal number literal. -static void readHexNumber(Parser* parser) -{ - // Skip past the `x` used to denote a hexadecimal literal. - nextChar(parser); - - // Iterate over all the valid hexadecimal digits found. - while (readHexDigit(parser) != -1) continue; - - makeNumber(parser, true); -} - -// Finishes lexing a number literal. -static void readNumber(Parser* parser) -{ - while (isDigit(peekChar(parser))) nextChar(parser); - - // See if it has a floating point. Make sure there is a digit after the "." - // so we don't get confused by method calls on number literals. - if (peekChar(parser) == '.' && isDigit(peekNextChar(parser))) - { - nextChar(parser); - while (isDigit(peekChar(parser))) nextChar(parser); - } - - // See if the number is in scientific notation. - if (matchChar(parser, 'e') || matchChar(parser, 'E')) - { - // Allow a single positive/negative exponent symbol. - if(!matchChar(parser, '+')) - { - matchChar(parser, '-'); - } - - if (!isDigit(peekChar(parser))) - { - lexError(parser, "Unterminated scientific notation."); - } - - while (isDigit(peekChar(parser))) nextChar(parser); - } - - makeNumber(parser, false); -} - -// Finishes lexing an identifier. Handles reserved words. -static void readName(Parser* parser, TokenType type, char firstChar) -{ - ByteBuffer string; - wrenByteBufferInit(&string); - wrenByteBufferWrite(parser->vm, &string, firstChar); - - while (isName(peekChar(parser)) || isDigit(peekChar(parser))) - { - char c = nextChar(parser); - wrenByteBufferWrite(parser->vm, &string, c); - } - - // Update the type if it's a keyword. - size_t length = parser->currentChar - parser->tokenStart; - for (int i = 0; keywords[i].identifier != NULL; i++) - { - if (length == keywords[i].length && - memcmp(parser->tokenStart, keywords[i].identifier, length) == 0) - { - type = keywords[i].tokenType; - break; - } - } - - parser->next.value = wrenNewStringLength(parser->vm, - (char*)string.data, string.count); - - wrenByteBufferClear(parser->vm, &string); - makeToken(parser, type); -} - -// Reads [digits] hex digits in a string literal and returns their number value. -static int readHexEscape(Parser* parser, int digits, const char* description) -{ - int value = 0; - for (int i = 0; i < digits; i++) - { - if (peekChar(parser) == '"' || peekChar(parser) == '\0') - { - lexError(parser, "Incomplete %s escape sequence.", description); - - // Don't consume it if it isn't expected. Keeps us from reading past the - // end of an unterminated string. - parser->currentChar--; - break; - } - - int digit = readHexDigit(parser); - if (digit == -1) - { - lexError(parser, "Invalid %s escape sequence.", description); - break; - } - - value = (value * 16) | digit; - } - - return value; -} - -// Reads a hex digit Unicode escape sequence in a string literal. -static void readUnicodeEscape(Parser* parser, ByteBuffer* string, int length) -{ - int value = readHexEscape(parser, length, "Unicode"); - - // Grow the buffer enough for the encoded result. - int numBytes = wrenUtf8EncodeNumBytes(value); - if (numBytes != 0) - { - wrenByteBufferFill(parser->vm, string, 0, numBytes); - wrenUtf8Encode(value, string->data + string->count - numBytes); - } -} - -static void readRawString(Parser* parser) -{ - ByteBuffer string; - wrenByteBufferInit(&string); - TokenType type = TOKEN_STRING; - - //consume the second and third " - nextChar(parser); - nextChar(parser); - - int skipStart = 0; - int firstNewline = -1; - - int skipEnd = -1; - int lastNewline = -1; - - for (;;) - { - char c = nextChar(parser); - char c1 = peekChar(parser); - char c2 = peekNextChar(parser); - - if (c == '\r') continue; - - if (c == '\n') { - lastNewline = string.count; - skipEnd = lastNewline; - firstNewline = firstNewline == -1 ? string.count : firstNewline; - } - - if (c == '"' && c1 == '"' && c2 == '"') break; - - bool isWhitespace = c == ' ' || c == '\t'; - skipEnd = c == '\n' || isWhitespace ? skipEnd : -1; - - // If we haven't seen a newline or other character yet, - // and still seeing whitespace, count the characters - // as skippable till we know otherwise - bool skippable = skipStart != -1 && isWhitespace && firstNewline == -1; - skipStart = skippable ? string.count + 1 : skipStart; - - // We've counted leading whitespace till we hit something else, - // but it's not a newline, so we reset skipStart since we need these characters - if (firstNewline == -1 && !isWhitespace && c != '\n') skipStart = -1; - - if (c == '\0' || c1 == '\0' || c2 == '\0') - { - lexError(parser, "Unterminated raw string."); - - // Don't consume it if it isn't expected. Keeps us from reading past the - // end of an unterminated string. - parser->currentChar--; - break; - } - - wrenByteBufferWrite(parser->vm, &string, c); - } - - //consume the second and third " - nextChar(parser); - nextChar(parser); - - int offset = 0; - int count = string.count; - - if(firstNewline != -1 && skipStart == firstNewline) offset = firstNewline + 1; - if(lastNewline != -1 && skipEnd == lastNewline) count = lastNewline; - - count -= (offset > count) ? count : offset; - - parser->next.value = wrenNewStringLength(parser->vm, - ((char*)string.data) + offset, count); - - wrenByteBufferClear(parser->vm, &string); - makeToken(parser, type); -} - -// Finishes lexing a string literal. -static void readString(Parser* parser) -{ - ByteBuffer string; - TokenType type = TOKEN_STRING; - wrenByteBufferInit(&string); - - for (;;) - { - char c = nextChar(parser); - if (c == '"') break; - if (c == '\r') continue; - - if (c == '\0') - { - lexError(parser, "Unterminated string."); - - // Don't consume it if it isn't expected. Keeps us from reading past the - // end of an unterminated string. - parser->currentChar--; - break; - } - - if (c == '%') - { - if (parser->numParens < MAX_INTERPOLATION_NESTING) - { - // TODO: Allow format string. - if (nextChar(parser) != '(') lexError(parser, "Expect '(' after '%%'."); - - parser->parens[parser->numParens++] = 1; - type = TOKEN_INTERPOLATION; - break; - } - - lexError(parser, "Interpolation may only nest %d levels deep.", - MAX_INTERPOLATION_NESTING); - } - - if (c == '\\') - { - switch (nextChar(parser)) - { - case '"': wrenByteBufferWrite(parser->vm, &string, '"'); break; - case '\\': wrenByteBufferWrite(parser->vm, &string, '\\'); break; - case '%': wrenByteBufferWrite(parser->vm, &string, '%'); break; - case '0': wrenByteBufferWrite(parser->vm, &string, '\0'); break; - case 'a': wrenByteBufferWrite(parser->vm, &string, '\a'); break; - case 'b': wrenByteBufferWrite(parser->vm, &string, '\b'); break; - case 'e': wrenByteBufferWrite(parser->vm, &string, '\33'); break; - case 'f': wrenByteBufferWrite(parser->vm, &string, '\f'); break; - case 'n': wrenByteBufferWrite(parser->vm, &string, '\n'); break; - case 'r': wrenByteBufferWrite(parser->vm, &string, '\r'); break; - case 't': wrenByteBufferWrite(parser->vm, &string, '\t'); break; - case 'u': readUnicodeEscape(parser, &string, 4); break; - case 'U': readUnicodeEscape(parser, &string, 8); break; - case 'v': wrenByteBufferWrite(parser->vm, &string, '\v'); break; - case 'x': - wrenByteBufferWrite(parser->vm, &string, - (uint8_t)readHexEscape(parser, 2, "byte")); - break; - - default: - lexError(parser, "Invalid escape character '%c'.", - *(parser->currentChar - 1)); - break; - } - } - else - { - wrenByteBufferWrite(parser->vm, &string, c); - } - } - - parser->next.value = wrenNewStringLength(parser->vm, - (char*)string.data, string.count); - - wrenByteBufferClear(parser->vm, &string); - makeToken(parser, type); -} - -// Lex the next token and store it in [parser.next]. -static void nextToken(Parser* parser) -{ - parser->previous = parser->current; - parser->current = parser->next; - - // If we are out of tokens, don't try to tokenize any more. We *do* still - // copy the TOKEN_EOF to previous so that code that expects it to be consumed - // will still work. - if (parser->next.type == TOKEN_EOF) return; - if (parser->current.type == TOKEN_EOF) return; - - while (peekChar(parser) != '\0') - { - parser->tokenStart = parser->currentChar; - - char c = nextChar(parser); - switch (c) - { - case '(': - // If we are inside an interpolated expression, count the unmatched "(". - if (parser->numParens > 0) parser->parens[parser->numParens - 1]++; - makeToken(parser, TOKEN_LEFT_PAREN); - return; - - case ')': - // If we are inside an interpolated expression, count the ")". - if (parser->numParens > 0 && - --parser->parens[parser->numParens - 1] == 0) - { - // This is the final ")", so the interpolation expression has ended. - // This ")" now begins the next section of the template string. - parser->numParens--; - readString(parser); - return; - } - - makeToken(parser, TOKEN_RIGHT_PAREN); - return; - - case '[': makeToken(parser, TOKEN_LEFT_BRACKET); return; - case ']': makeToken(parser, TOKEN_RIGHT_BRACKET); return; - case '{': makeToken(parser, TOKEN_LEFT_BRACE); return; - case '}': makeToken(parser, TOKEN_RIGHT_BRACE); return; - case ':': makeToken(parser, TOKEN_COLON); return; - case ',': makeToken(parser, TOKEN_COMMA); return; - case '*': makeToken(parser, TOKEN_STAR); return; - case '%': makeToken(parser, TOKEN_PERCENT); return; - case '#': { - // Ignore shebang on the first line. - if (parser->currentLine == 1 && peekChar(parser) == '!' && peekNextChar(parser) == '/') - { - skipLineComment(parser); - break; - } - // Otherwise we treat it as a token - makeToken(parser, TOKEN_HASH); - return; - } - case '^': makeToken(parser, TOKEN_CARET); return; - case '+': makeToken(parser, TOKEN_PLUS); return; - case '-': makeToken(parser, TOKEN_MINUS); return; - case '~': makeToken(parser, TOKEN_TILDE); return; - case '?': makeToken(parser, TOKEN_QUESTION); return; - - case '|': twoCharToken(parser, '|', TOKEN_PIPEPIPE, TOKEN_PIPE); return; - case '&': twoCharToken(parser, '&', TOKEN_AMPAMP, TOKEN_AMP); return; - case '=': twoCharToken(parser, '=', TOKEN_EQEQ, TOKEN_EQ); return; - case '!': twoCharToken(parser, '=', TOKEN_BANGEQ, TOKEN_BANG); return; - - case '.': - if (matchChar(parser, '.')) - { - twoCharToken(parser, '.', TOKEN_DOTDOTDOT, TOKEN_DOTDOT); - return; - } - - makeToken(parser, TOKEN_DOT); - return; - - case '/': - if (matchChar(parser, '/')) - { - skipLineComment(parser); - break; - } - - if (matchChar(parser, '*')) - { - skipBlockComment(parser); - break; - } - - makeToken(parser, TOKEN_SLASH); - return; - - case '<': - if (matchChar(parser, '<')) - { - makeToken(parser, TOKEN_LTLT); - } - else - { - twoCharToken(parser, '=', TOKEN_LTEQ, TOKEN_LT); - } - return; - - case '>': - if (matchChar(parser, '>')) - { - makeToken(parser, TOKEN_GTGT); - } - else - { - twoCharToken(parser, '=', TOKEN_GTEQ, TOKEN_GT); - } - return; - - case '\n': - makeToken(parser, TOKEN_LINE); - return; - - case ' ': - case '\r': - case '\t': - // Skip forward until we run out of whitespace. - while (peekChar(parser) == ' ' || - peekChar(parser) == '\r' || - peekChar(parser) == '\t') - { - nextChar(parser); - } - break; - - case '"': { - if(peekChar(parser) == '"' && peekNextChar(parser) == '"') { - readRawString(parser); - return; - } - readString(parser); return; - } - case '_': - readName(parser, - peekChar(parser) == '_' ? TOKEN_STATIC_FIELD : TOKEN_FIELD, c); - return; - - case '0': - if (peekChar(parser) == 'x') - { - readHexNumber(parser); - return; - } - - readNumber(parser); - return; - - default: - if (isName(c)) - { - readName(parser, TOKEN_NAME, c); - } - else if (isDigit(c)) - { - readNumber(parser); - } - else - { - if (c >= 32 && c <= 126) - { - lexError(parser, "Invalid character '%c'.", c); - } - else - { - // Don't show non-ASCII values since we didn't UTF-8 decode the - // bytes. Since there are no non-ASCII byte values that are - // meaningful code units in Wren, the lexer works on raw bytes, - // even though the source code and console output are UTF-8. - lexError(parser, "Invalid byte 0x%x.", (uint8_t)c); - } - parser->next.type = TOKEN_ERROR; - parser->next.length = 0; - } - return; - } - } - - // If we get here, we're out of source, so just make EOF tokens. - parser->tokenStart = parser->currentChar; - makeToken(parser, TOKEN_EOF); -} - -// Parsing --------------------------------------------------------------------- - -// Returns the type of the current token. -static TokenType peek(Compiler* compiler) -{ - return compiler->parser->current.type; -} - -// Returns the type of the current token. -static TokenType peekNext(Compiler* compiler) -{ - return compiler->parser->next.type; -} - -// Consumes the current token if its type is [expected]. Returns true if a -// token was consumed. -static bool match(Compiler* compiler, TokenType expected) -{ - if (peek(compiler) != expected) return false; - - nextToken(compiler->parser); - return true; -} - -// Consumes the current token. Emits an error if its type is not [expected]. -static void consume(Compiler* compiler, TokenType expected, - const char* errorMessage) -{ - nextToken(compiler->parser); - if (compiler->parser->previous.type != expected) - { - error(compiler, errorMessage); - - // If the next token is the one we want, assume the current one is just a - // spurious error and discard it to minimize the number of cascaded errors. - if (compiler->parser->current.type == expected) nextToken(compiler->parser); - } -} - -// Matches one or more newlines. Returns true if at least one was found. -static bool matchLine(Compiler* compiler) -{ - if (!match(compiler, TOKEN_LINE)) return false; - - while (match(compiler, TOKEN_LINE)); - return true; -} - -// Discards any newlines starting at the current token. -static void ignoreNewlines(Compiler* compiler) -{ - matchLine(compiler); -} - -// Consumes the current token. Emits an error if it is not a newline. Then -// discards any duplicate newlines following it. -static void consumeLine(Compiler* compiler, const char* errorMessage) -{ - consume(compiler, TOKEN_LINE, errorMessage); - ignoreNewlines(compiler); -} - -static void allowLineBeforeDot(Compiler* compiler) { - if (peek(compiler) == TOKEN_LINE && peekNext(compiler) == TOKEN_DOT) { - nextToken(compiler->parser); - } -} - -// Variables and scopes -------------------------------------------------------- - -// Emits one single-byte argument. Returns its index. -static int emitByte(Compiler* compiler, int byte) -{ - wrenByteBufferWrite(compiler->parser->vm, &compiler->fn->code, (uint8_t)byte); - - // Assume the instruction is associated with the most recently consumed token. - wrenIntBufferWrite(compiler->parser->vm, &compiler->fn->debug->sourceLines, - compiler->parser->previous.line); - - return compiler->fn->code.count - 1; -} - -// Emits one bytecode instruction. -static void emitOp(Compiler* compiler, Code instruction) -{ - emitByte(compiler, instruction); - - // Keep track of the stack's high water mark. - compiler->numSlots += stackEffects[instruction]; - if (compiler->numSlots > compiler->fn->maxSlots) - { - compiler->fn->maxSlots = compiler->numSlots; - } -} - -// Emits one 16-bit argument, which will be written big endian. -static void emitShort(Compiler* compiler, int arg) -{ - emitByte(compiler, (arg >> 8) & 0xff); - emitByte(compiler, arg & 0xff); -} - -// Emits one bytecode instruction followed by a 8-bit argument. Returns the -// index of the argument in the bytecode. -static int emitByteArg(Compiler* compiler, Code instruction, int arg) -{ - emitOp(compiler, instruction); - return emitByte(compiler, arg); -} - -// Emits one bytecode instruction followed by a 16-bit argument, which will be -// written big endian. -static void emitShortArg(Compiler* compiler, Code instruction, int arg) -{ - emitOp(compiler, instruction); - emitShort(compiler, arg); -} - -// Emits [instruction] followed by a placeholder for a jump offset. The -// placeholder can be patched by calling [jumpPatch]. Returns the index of the -// placeholder. -static int emitJump(Compiler* compiler, Code instruction) -{ - emitOp(compiler, instruction); - emitByte(compiler, 0xff); - return emitByte(compiler, 0xff) - 1; -} - -// Creates a new constant for the current value and emits the bytecode to load -// it from the constant table. -static void emitConstant(Compiler* compiler, Value value) -{ - int constant = addConstant(compiler, value); - - // Compile the code to load the constant. - emitShortArg(compiler, CODE_CONSTANT, constant); -} - -// Create a new local variable with [name]. Assumes the current scope is local -// and the name is unique. -static int addLocal(Compiler* compiler, const char* name, int length) -{ - Local* local = &compiler->locals[compiler->numLocals]; - local->name = name; - local->length = length; - local->depth = compiler->scopeDepth; - local->isUpvalue = false; - return compiler->numLocals++; -} - -// Declares a variable in the current scope whose name is the given token. -// -// If [token] is `NULL`, uses the previously consumed token. Returns its symbol. -static int declareVariable(Compiler* compiler, Token* token) -{ - if (token == NULL) token = &compiler->parser->previous; - - if (token->length > MAX_VARIABLE_NAME) - { - error(compiler, "Variable name cannot be longer than %d characters.", - MAX_VARIABLE_NAME); - } - - // Top-level module scope. - if (compiler->scopeDepth == -1) - { - int line = -1; - int symbol = wrenDefineVariable(compiler->parser->vm, - compiler->parser->module, - token->start, token->length, - NULL_VAL, &line); - - if (symbol == -1) - { - error(compiler, "Module variable is already defined."); - } - else if (symbol == -2) - { - error(compiler, "Too many module variables defined."); - } - else if (symbol == -3) - { - error(compiler, - "Variable '%.*s' referenced before this definition (first use at line %d).", - token->length, token->start, line); - } - - return symbol; - } - - // See if there is already a variable with this name declared in the current - // scope. (Outer scopes are OK: those get shadowed.) - for (int i = compiler->numLocals - 1; i >= 0; i--) - { - Local* local = &compiler->locals[i]; - - // Once we escape this scope and hit an outer one, we can stop. - if (local->depth < compiler->scopeDepth) break; - - if (local->length == token->length && - memcmp(local->name, token->start, token->length) == 0) - { - error(compiler, "Variable is already declared in this scope."); - return i; - } - } - - if (compiler->numLocals == MAX_LOCALS) - { - error(compiler, "Cannot declare more than %d variables in one scope.", - MAX_LOCALS); - return -1; - } - - return addLocal(compiler, token->start, token->length); -} - -// Parses a name token and declares a variable in the current scope with that -// name. Returns its slot. -static int declareNamedVariable(Compiler* compiler) -{ - consume(compiler, TOKEN_NAME, "Expect variable name."); - return declareVariable(compiler, NULL); -} - -// Stores a variable with the previously defined symbol in the current scope. -static void defineVariable(Compiler* compiler, int symbol) -{ - // Store the variable. If it's a local, the result of the initializer is - // in the correct slot on the stack already so we're done. - if (compiler->scopeDepth >= 0) return; - - // It's a module-level variable, so store the value in the module slot and - // then discard the temporary for the initializer. - emitShortArg(compiler, CODE_STORE_MODULE_VAR, symbol); - emitOp(compiler, CODE_POP); -} - -// Starts a new local block scope. -static void pushScope(Compiler* compiler) -{ - compiler->scopeDepth++; -} - -// Generates code to discard local variables at [depth] or greater. Does *not* -// actually undeclare variables or pop any scopes, though. This is called -// directly when compiling "break" statements to ditch the local variables -// before jumping out of the loop even though they are still in scope *past* -// the break instruction. -// -// Returns the number of local variables that were eliminated. -static int discardLocals(Compiler* compiler, int depth) -{ - ASSERT(compiler->scopeDepth > -1, "Cannot exit top-level scope."); - - int local = compiler->numLocals - 1; - while (local >= 0 && compiler->locals[local].depth >= depth) - { - // If the local was closed over, make sure the upvalue gets closed when it - // goes out of scope on the stack. We use emitByte() and not emitOp() here - // because we don't want to track that stack effect of these pops since the - // variables are still in scope after the break. - if (compiler->locals[local].isUpvalue) - { - emitByte(compiler, CODE_CLOSE_UPVALUE); - } - else - { - emitByte(compiler, CODE_POP); - } - - - local--; - } - - return compiler->numLocals - local - 1; -} - -// Closes the last pushed block scope and discards any local variables declared -// in that scope. This should only be called in a statement context where no -// temporaries are still on the stack. -static void popScope(Compiler* compiler) -{ - int popped = discardLocals(compiler, compiler->scopeDepth); - compiler->numLocals -= popped; - compiler->numSlots -= popped; - compiler->scopeDepth--; -} - -// Attempts to look up the name in the local variables of [compiler]. If found, -// returns its index, otherwise returns -1. -static int resolveLocal(Compiler* compiler, const char* name, int length) -{ - // Look it up in the local scopes. Look in reverse order so that the most - // nested variable is found first and shadows outer ones. - for (int i = compiler->numLocals - 1; i >= 0; i--) - { - if (compiler->locals[i].length == length && - memcmp(name, compiler->locals[i].name, length) == 0) - { - return i; - } - } - - return -1; -} - -// Adds an upvalue to [compiler]'s function with the given properties. Does not -// add one if an upvalue for that variable is already in the list. Returns the -// index of the upvalue. -static int addUpvalue(Compiler* compiler, bool isLocal, int index) -{ - // Look for an existing one. - for (int i = 0; i < compiler->fn->numUpvalues; i++) - { - CompilerUpvalue* upvalue = &compiler->upvalues[i]; - if (upvalue->index == index && upvalue->isLocal == isLocal) return i; - } - - // If we got here, it's a new upvalue. - compiler->upvalues[compiler->fn->numUpvalues].isLocal = isLocal; - compiler->upvalues[compiler->fn->numUpvalues].index = index; - return compiler->fn->numUpvalues++; -} - -// Attempts to look up [name] in the functions enclosing the one being compiled -// by [compiler]. If found, it adds an upvalue for it to this compiler's list -// of upvalues (unless it's already in there) and returns its index. If not -// found, returns -1. -// -// If the name is found outside of the immediately enclosing function, this -// will flatten the closure and add upvalues to all of the intermediate -// functions so that it gets walked down to this one. -// -// If it reaches a method boundary, this stops and returns -1 since methods do -// not close over local variables. -static int findUpvalue(Compiler* compiler, const char* name, int length) -{ - // If we are at the top level, we didn't find it. - if (compiler->parent == NULL) return -1; - - // If we hit the method boundary (and the name isn't a static field), then - // stop looking for it. We'll instead treat it as a self send. - if (name[0] != '_' && compiler->parent->enclosingClass != NULL) return -1; - - // See if it's a local variable in the immediately enclosing function. - int local = resolveLocal(compiler->parent, name, length); - if (local != -1) - { - // Mark the local as an upvalue so we know to close it when it goes out of - // scope. - compiler->parent->locals[local].isUpvalue = true; - - return addUpvalue(compiler, true, local); - } - - // See if it's an upvalue in the immediately enclosing function. In other - // words, if it's a local variable in a non-immediately enclosing function. - // This "flattens" closures automatically: it adds upvalues to all of the - // intermediate functions to get from the function where a local is declared - // all the way into the possibly deeply nested function that is closing over - // it. - int upvalue = findUpvalue(compiler->parent, name, length); - if (upvalue != -1) - { - return addUpvalue(compiler, false, upvalue); - } - - // If we got here, we walked all the way up the parent chain and couldn't - // find it. - return -1; -} - -// Look up [name] in the current scope to see what variable it refers to. -// Returns the variable either in local scope, or the enclosing function's -// upvalue list. Does not search the module scope. Returns a variable with -// index -1 if not found. -static Variable resolveNonmodule(Compiler* compiler, - const char* name, int length) -{ - // Look it up in the local scopes. - Variable variable; - variable.scope = SCOPE_LOCAL; - variable.index = resolveLocal(compiler, name, length); - if (variable.index != -1) return variable; - - // Tt's not a local, so guess that it's an upvalue. - variable.scope = SCOPE_UPVALUE; - variable.index = findUpvalue(compiler, name, length); - return variable; -} - -// Look up [name] in the current scope to see what variable it refers to. -// Returns the variable either in module scope, local scope, or the enclosing -// function's upvalue list. Returns a variable with index -1 if not found. -static Variable resolveName(Compiler* compiler, const char* name, int length) -{ - Variable variable = resolveNonmodule(compiler, name, length); - if (variable.index != -1) return variable; - - variable.scope = SCOPE_MODULE; - variable.index = wrenSymbolTableFind(&compiler->parser->module->variableNames, - name, length); - return variable; -} - -static void loadLocal(Compiler* compiler, int slot) -{ - if (slot <= 8) - { - emitOp(compiler, (Code)(CODE_LOAD_LOCAL_0 + slot)); - return; - } - - emitByteArg(compiler, CODE_LOAD_LOCAL, slot); -} - -// Finishes [compiler], which is compiling a function, method, or chunk of top -// level code. If there is a parent compiler, then this emits code in the -// parent compiler to load the resulting function. -static ObjFn* endCompiler(Compiler* compiler, - const char* debugName, int debugNameLength) -{ - // If we hit an error, don't finish the function since it's borked anyway. - if (compiler->parser->hasError) - { - compiler->parser->vm->compiler = compiler->parent; - return NULL; - } - - // Mark the end of the bytecode. Since it may contain multiple early returns, - // we can't rely on CODE_RETURN to tell us we're at the end. - emitOp(compiler, CODE_END); - - wrenFunctionBindName(compiler->parser->vm, compiler->fn, - debugName, debugNameLength); - - // In the function that contains this one, load the resulting function object. - if (compiler->parent != NULL) - { - int constant = addConstant(compiler->parent, OBJ_VAL(compiler->fn)); - - // Wrap the function in a closure. We do this even if it has no upvalues so - // that the VM can uniformly assume all called objects are closures. This - // makes creating a function a little slower, but makes invoking them - // faster. Given that functions are invoked more often than they are - // created, this is a win. - emitShortArg(compiler->parent, CODE_CLOSURE, constant); - - // Emit arguments for each upvalue to know whether to capture a local or - // an upvalue. - for (int i = 0; i < compiler->fn->numUpvalues; i++) - { - emitByte(compiler->parent, compiler->upvalues[i].isLocal ? 1 : 0); - emitByte(compiler->parent, compiler->upvalues[i].index); - } - } - - // Pop this compiler off the stack. - compiler->parser->vm->compiler = compiler->parent; - - #if WREN_DEBUG_DUMP_COMPILED_CODE - wrenDumpCode(compiler->parser->vm, compiler->fn); - #endif - - return compiler->fn; -} - -// Grammar --------------------------------------------------------------------- - -typedef enum -{ - PREC_NONE, - PREC_LOWEST, - PREC_ASSIGNMENT, // = - PREC_CONDITIONAL, // ?: - PREC_LOGICAL_OR, // || - PREC_LOGICAL_AND, // && - PREC_EQUALITY, // == != - PREC_IS, // is - PREC_COMPARISON, // < > <= >= - PREC_BITWISE_OR, // | - PREC_BITWISE_XOR, // ^ - PREC_BITWISE_AND, // & - PREC_BITWISE_SHIFT, // << >> - PREC_RANGE, // .. ... - PREC_TERM, // + - - PREC_FACTOR, // * / % - PREC_UNARY, // unary - ! ~ - PREC_CALL, // . () [] - PREC_PRIMARY -} Precedence; - -typedef void (*GrammarFn)(Compiler*, bool canAssign); - -typedef void (*SignatureFn)(Compiler* compiler, Signature* signature); - -typedef struct -{ - GrammarFn prefix; - GrammarFn infix; - SignatureFn method; - Precedence precedence; - const char* name; -} GrammarRule; - -// Forward declarations since the grammar is recursive. -static GrammarRule* getRule(TokenType type); -static void expression(Compiler* compiler); -static void statement(Compiler* compiler); -static void definition(Compiler* compiler); -static void parsePrecedence(Compiler* compiler, Precedence precedence); - -// Replaces the placeholder argument for a previous CODE_JUMP or CODE_JUMP_IF -// instruction with an offset that jumps to the current end of bytecode. -static void patchJump(Compiler* compiler, int offset) -{ - // -2 to adjust for the bytecode for the jump offset itself. - int jump = compiler->fn->code.count - offset - 2; - if (jump > MAX_JUMP) error(compiler, "Too much code to jump over."); - - compiler->fn->code.data[offset] = (jump >> 8) & 0xff; - compiler->fn->code.data[offset + 1] = jump & 0xff; -} - -// Parses a block body, after the initial "{" has been consumed. -// -// Returns true if it was a expression body, false if it was a statement body. -// (More precisely, returns true if a value was left on the stack. An empty -// block returns false.) -static bool finishBlock(Compiler* compiler) -{ - // Empty blocks do nothing. - if (match(compiler, TOKEN_RIGHT_BRACE)) return false; - - // If there's no line after the "{", it's a single-expression body. - if (!matchLine(compiler)) - { - expression(compiler); - consume(compiler, TOKEN_RIGHT_BRACE, "Expect '}' at end of block."); - return true; - } - - // Empty blocks (with just a newline inside) do nothing. - if (match(compiler, TOKEN_RIGHT_BRACE)) return false; - - // Compile the definition list. - do - { - definition(compiler); - consumeLine(compiler, "Expect newline after statement."); - } - while (peek(compiler) != TOKEN_RIGHT_BRACE && peek(compiler) != TOKEN_EOF); - - consume(compiler, TOKEN_RIGHT_BRACE, "Expect '}' at end of block."); - return false; -} - -// Parses a method or function body, after the initial "{" has been consumed. -// -// If [Compiler->isInitializer] is `true`, this is the body of a constructor -// initializer. In that case, this adds the code to ensure it returns `this`. -static void finishBody(Compiler* compiler) -{ - bool isExpressionBody = finishBlock(compiler); - - if (compiler->isInitializer) - { - // If the initializer body evaluates to a value, discard it. - if (isExpressionBody) emitOp(compiler, CODE_POP); - - // The receiver is always stored in the first local slot. - emitOp(compiler, CODE_LOAD_LOCAL_0); - } - else if (!isExpressionBody) - { - // Implicitly return null in statement bodies. - emitOp(compiler, CODE_NULL); - } - - emitOp(compiler, CODE_RETURN); -} - -// The VM can only handle a certain number of parameters, so check that we -// haven't exceeded that and give a usable error. -static void validateNumParameters(Compiler* compiler, int numArgs) -{ - if (numArgs == MAX_PARAMETERS + 1) - { - // Only show an error at exactly max + 1 so that we can keep parsing the - // parameters and minimize cascaded errors. - error(compiler, "Methods cannot have more than %d parameters.", - MAX_PARAMETERS); - } -} - -// Parses the rest of a comma-separated parameter list after the opening -// delimeter. Updates `arity` in [signature] with the number of parameters. -static void finishParameterList(Compiler* compiler, Signature* signature) -{ - do - { - ignoreNewlines(compiler); - validateNumParameters(compiler, ++signature->arity); - - // Define a local variable in the method for the parameter. - declareNamedVariable(compiler); - } - while (match(compiler, TOKEN_COMMA)); -} - -// Gets the symbol for a method [name] with [length]. -static int methodSymbol(Compiler* compiler, const char* name, int length) -{ - return wrenSymbolTableEnsure(compiler->parser->vm, - &compiler->parser->vm->methodNames, name, length); -} - -// Appends characters to [name] (and updates [length]) for [numParams] "_" -// surrounded by [leftBracket] and [rightBracket]. -static void signatureParameterList(char name[MAX_METHOD_SIGNATURE], int* length, - int numParams, char leftBracket, char rightBracket) -{ - name[(*length)++] = leftBracket; - - // This function may be called with too many parameters. When that happens, - // a compile error has already been reported, but we need to make sure we - // don't overflow the string too, hence the MAX_PARAMETERS check. - for (int i = 0; i < numParams && i < MAX_PARAMETERS; i++) - { - if (i > 0) name[(*length)++] = ','; - name[(*length)++] = '_'; - } - name[(*length)++] = rightBracket; -} - -// Fills [name] with the stringified version of [signature] and updates -// [length] to the resulting length. -static void signatureToString(Signature* signature, - char name[MAX_METHOD_SIGNATURE], int* length) -{ - *length = 0; - - // Build the full name from the signature. - memcpy(name + *length, signature->name, signature->length); - *length += signature->length; - - switch (signature->type) - { - case SIG_METHOD: - signatureParameterList(name, length, signature->arity, '(', ')'); - break; - - case SIG_GETTER: - // The signature is just the name. - break; - - case SIG_SETTER: - name[(*length)++] = '='; - signatureParameterList(name, length, 1, '(', ')'); - break; - - case SIG_SUBSCRIPT: - signatureParameterList(name, length, signature->arity, '[', ']'); - break; - - case SIG_SUBSCRIPT_SETTER: - signatureParameterList(name, length, signature->arity - 1, '[', ']'); - name[(*length)++] = '='; - signatureParameterList(name, length, 1, '(', ')'); - break; - - case SIG_INITIALIZER: - memcpy(name, "init ", 5); - memcpy(name + 5, signature->name, signature->length); - *length = 5 + signature->length; - signatureParameterList(name, length, signature->arity, '(', ')'); - break; - } - - name[*length] = '\0'; -} - -// Gets the symbol for a method with [signature]. -static int signatureSymbol(Compiler* compiler, Signature* signature) -{ - // Build the full name from the signature. - char name[MAX_METHOD_SIGNATURE]; - int length; - signatureToString(signature, name, &length); - - return methodSymbol(compiler, name, length); -} - -// Returns a signature with [type] whose name is from the last consumed token. -static Signature signatureFromToken(Compiler* compiler, SignatureType type) -{ - Signature signature; - - // Get the token for the method name. - Token* token = &compiler->parser->previous; - signature.name = token->start; - signature.length = token->length; - signature.type = type; - signature.arity = 0; - - if (signature.length > MAX_METHOD_NAME) - { - error(compiler, "Method names cannot be longer than %d characters.", - MAX_METHOD_NAME); - signature.length = MAX_METHOD_NAME; - } - - return signature; -} - -// Parses a comma-separated list of arguments. Modifies [signature] to include -// the arity of the argument list. -static void finishArgumentList(Compiler* compiler, Signature* signature) -{ - do - { - ignoreNewlines(compiler); - validateNumParameters(compiler, ++signature->arity); - expression(compiler); - } - while (match(compiler, TOKEN_COMMA)); - - // Allow a newline before the closing delimiter. - ignoreNewlines(compiler); -} - -// Compiles a method call with [signature] using [instruction]. -static void callSignature(Compiler* compiler, Code instruction, - Signature* signature) -{ - int symbol = signatureSymbol(compiler, signature); - emitShortArg(compiler, (Code)(instruction + signature->arity), symbol); - - if (instruction == CODE_SUPER_0) - { - // Super calls need to be statically bound to the class's superclass. This - // ensures we call the right method even when a method containing a super - // call is inherited by another subclass. - // - // We bind it at class definition time by storing a reference to the - // superclass in a constant. So, here, we create a slot in the constant - // table and store NULL in it. When the method is bound, we'll look up the - // superclass then and store it in the constant slot. - emitShort(compiler, addConstant(compiler, NULL_VAL)); - } -} - -// Compiles a method call with [numArgs] for a method with [name] with [length]. -static void callMethod(Compiler* compiler, int numArgs, const char* name, - int length) -{ - int symbol = methodSymbol(compiler, name, length); - emitShortArg(compiler, (Code)(CODE_CALL_0 + numArgs), symbol); -} - -// Compiles an (optional) argument list for a method call with [methodSignature] -// and then calls it. -static void methodCall(Compiler* compiler, Code instruction, - Signature* signature) -{ - // Make a new signature that contains the updated arity and type based on - // the arguments we find. - Signature called = { signature->name, signature->length, SIG_GETTER, 0 }; - - // Parse the argument list, if any. - if (match(compiler, TOKEN_LEFT_PAREN)) - { - called.type = SIG_METHOD; - - // Allow new line before an empty argument list - ignoreNewlines(compiler); - - // Allow empty an argument list. - if (peek(compiler) != TOKEN_RIGHT_PAREN) - { - finishArgumentList(compiler, &called); - } - consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after arguments."); - } - - // Parse the block argument, if any. - if (match(compiler, TOKEN_LEFT_BRACE)) - { - // Include the block argument in the arity. - called.type = SIG_METHOD; - called.arity++; - - Compiler fnCompiler; - initCompiler(&fnCompiler, compiler->parser, compiler, false); - - // Make a dummy signature to track the arity. - Signature fnSignature = { "", 0, SIG_METHOD, 0 }; - - // Parse the parameter list, if any. - if (match(compiler, TOKEN_PIPE)) - { - finishParameterList(&fnCompiler, &fnSignature); - consume(compiler, TOKEN_PIPE, "Expect '|' after function parameters."); - } - - fnCompiler.fn->arity = fnSignature.arity; - - finishBody(&fnCompiler); - - // Name the function based on the method its passed to. - char blockName[MAX_METHOD_SIGNATURE + 15]; - int blockLength; - signatureToString(&called, blockName, &blockLength); - memmove(blockName + blockLength, " block argument", 16); - - endCompiler(&fnCompiler, blockName, blockLength + 15); - } - - // TODO: Allow Grace-style mixfix methods? - - // If this is a super() call for an initializer, make sure we got an actual - // argument list. - if (signature->type == SIG_INITIALIZER) - { - if (called.type != SIG_METHOD) - { - error(compiler, "A superclass constructor must have an argument list."); - } - - called.type = SIG_INITIALIZER; - } - - callSignature(compiler, instruction, &called); -} - -// Compiles a call whose name is the previously consumed token. This includes -// getters, method calls with arguments, and setter calls. -static void namedCall(Compiler* compiler, bool canAssign, Code instruction) -{ - // Get the token for the method name. - Signature signature = signatureFromToken(compiler, SIG_GETTER); - - if (canAssign && match(compiler, TOKEN_EQ)) - { - ignoreNewlines(compiler); - - // Build the setter signature. - signature.type = SIG_SETTER; - signature.arity = 1; - - // Compile the assigned value. - expression(compiler); - callSignature(compiler, instruction, &signature); - } - else - { - methodCall(compiler, instruction, &signature); - allowLineBeforeDot(compiler); - } -} - -// Emits the code to load [variable] onto the stack. -static void loadVariable(Compiler* compiler, Variable variable) -{ - switch (variable.scope) - { - case SCOPE_LOCAL: - loadLocal(compiler, variable.index); - break; - case SCOPE_UPVALUE: - emitByteArg(compiler, CODE_LOAD_UPVALUE, variable.index); - break; - case SCOPE_MODULE: - emitShortArg(compiler, CODE_LOAD_MODULE_VAR, variable.index); - break; - default: - UNREACHABLE(); - } -} - -// Loads the receiver of the currently enclosing method. Correctly handles -// functions defined inside methods. -static void loadThis(Compiler* compiler) -{ - loadVariable(compiler, resolveNonmodule(compiler, "this", 4)); -} - -// Pushes the value for a module-level variable implicitly imported from core. -static void loadCoreVariable(Compiler* compiler, const char* name) -{ - int symbol = wrenSymbolTableFind(&compiler->parser->module->variableNames, - name, strlen(name)); - ASSERT(symbol != -1, "Should have already defined core name."); - emitShortArg(compiler, CODE_LOAD_MODULE_VAR, symbol); -} - -// A parenthesized expression. -static void grouping(Compiler* compiler, bool canAssign) -{ - expression(compiler); - consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after expression."); -} - -// A list literal. -static void list(Compiler* compiler, bool canAssign) -{ - // Instantiate a new list. - loadCoreVariable(compiler, "List"); - callMethod(compiler, 0, "new()", 5); - - // Compile the list elements. Each one compiles to a ".add()" call. - do - { - ignoreNewlines(compiler); - - // Stop if we hit the end of the list. - if (peek(compiler) == TOKEN_RIGHT_BRACKET) break; - - // The element. - expression(compiler); - callMethod(compiler, 1, "addCore_(_)", 11); - } while (match(compiler, TOKEN_COMMA)); - - // Allow newlines before the closing ']'. - ignoreNewlines(compiler); - consume(compiler, TOKEN_RIGHT_BRACKET, "Expect ']' after list elements."); -} - -// A map literal. -static void map(Compiler* compiler, bool canAssign) -{ - // Instantiate a new map. - loadCoreVariable(compiler, "Map"); - callMethod(compiler, 0, "new()", 5); - - // Compile the map elements. Each one is compiled to just invoke the - // subscript setter on the map. - do - { - ignoreNewlines(compiler); - - // Stop if we hit the end of the map. - if (peek(compiler) == TOKEN_RIGHT_BRACE) break; - - // The key. - parsePrecedence(compiler, PREC_UNARY); - consume(compiler, TOKEN_COLON, "Expect ':' after map key."); - ignoreNewlines(compiler); - - // The value. - expression(compiler); - callMethod(compiler, 2, "addCore_(_,_)", 13); - } while (match(compiler, TOKEN_COMMA)); - - // Allow newlines before the closing '}'. - ignoreNewlines(compiler); - consume(compiler, TOKEN_RIGHT_BRACE, "Expect '}' after map entries."); -} - -// Unary operators like `-foo`. -static void unaryOp(Compiler* compiler, bool canAssign) -{ - GrammarRule* rule = getRule(compiler->parser->previous.type); - - ignoreNewlines(compiler); - - // Compile the argument. - parsePrecedence(compiler, (Precedence)(PREC_UNARY + 1)); - - // Call the operator method on the left-hand side. - callMethod(compiler, 0, rule->name, 1); -} - -static void boolean(Compiler* compiler, bool canAssign) -{ - emitOp(compiler, - compiler->parser->previous.type == TOKEN_FALSE ? CODE_FALSE : CODE_TRUE); -} - -// Walks the compiler chain to find the compiler for the nearest class -// enclosing this one. Returns NULL if not currently inside a class definition. -static Compiler* getEnclosingClassCompiler(Compiler* compiler) -{ - while (compiler != NULL) - { - if (compiler->enclosingClass != NULL) return compiler; - compiler = compiler->parent; - } - - return NULL; -} - -// Walks the compiler chain to find the nearest class enclosing this one. -// Returns NULL if not currently inside a class definition. -static ClassInfo* getEnclosingClass(Compiler* compiler) -{ - compiler = getEnclosingClassCompiler(compiler); - return compiler == NULL ? NULL : compiler->enclosingClass; -} - -static void field(Compiler* compiler, bool canAssign) -{ - // Initialize it with a fake value so we can keep parsing and minimize the - // number of cascaded errors. - int field = MAX_FIELDS; - - ClassInfo* enclosingClass = getEnclosingClass(compiler); - - if (enclosingClass == NULL) - { - error(compiler, "Cannot reference a field outside of a class definition."); - } - else if (enclosingClass->isForeign) - { - error(compiler, "Cannot define fields in a foreign class."); - } - else if (enclosingClass->inStatic) - { - error(compiler, "Cannot use an instance field in a static method."); - } - else - { - // Look up the field, or implicitly define it. - field = wrenSymbolTableEnsure(compiler->parser->vm, &enclosingClass->fields, - compiler->parser->previous.start, - compiler->parser->previous.length); - - if (field >= MAX_FIELDS) - { - error(compiler, "A class can only have %d fields.", MAX_FIELDS); - } - } - - // If there's an "=" after a field name, it's an assignment. - bool isLoad = true; - if (canAssign && match(compiler, TOKEN_EQ)) - { - // Compile the right-hand side. - expression(compiler); - isLoad = false; - } - - // If we're directly inside a method, use a more optimal instruction. - if (compiler->parent != NULL && - compiler->parent->enclosingClass == enclosingClass) - { - emitByteArg(compiler, isLoad ? CODE_LOAD_FIELD_THIS : CODE_STORE_FIELD_THIS, - field); - } - else - { - loadThis(compiler); - emitByteArg(compiler, isLoad ? CODE_LOAD_FIELD : CODE_STORE_FIELD, field); - } - - allowLineBeforeDot(compiler); -} - -// Compiles a read or assignment to [variable]. -static void bareName(Compiler* compiler, bool canAssign, Variable variable) -{ - // If there's an "=" after a bare name, it's a variable assignment. - if (canAssign && match(compiler, TOKEN_EQ)) - { - // Compile the right-hand side. - expression(compiler); - - // Emit the store instruction. - switch (variable.scope) - { - case SCOPE_LOCAL: - emitByteArg(compiler, CODE_STORE_LOCAL, variable.index); - break; - case SCOPE_UPVALUE: - emitByteArg(compiler, CODE_STORE_UPVALUE, variable.index); - break; - case SCOPE_MODULE: - emitShortArg(compiler, CODE_STORE_MODULE_VAR, variable.index); - break; - default: - UNREACHABLE(); - } - return; - } - - // Emit the load instruction. - loadVariable(compiler, variable); - - allowLineBeforeDot(compiler); -} - -static void staticField(Compiler* compiler, bool canAssign) -{ - Compiler* classCompiler = getEnclosingClassCompiler(compiler); - if (classCompiler == NULL) - { - error(compiler, "Cannot use a static field outside of a class definition."); - return; - } - - // Look up the name in the scope chain. - Token* token = &compiler->parser->previous; - - // If this is the first time we've seen this static field, implicitly - // define it as a variable in the scope surrounding the class definition. - if (resolveLocal(classCompiler, token->start, token->length) == -1) - { - int symbol = declareVariable(classCompiler, NULL); - - // Implicitly initialize it to null. - emitOp(classCompiler, CODE_NULL); - defineVariable(classCompiler, symbol); - } - - // It definitely exists now, so resolve it properly. This is different from - // the above resolveLocal() call because we may have already closed over it - // as an upvalue. - Variable variable = resolveName(compiler, token->start, token->length); - bareName(compiler, canAssign, variable); -} - -// Compiles a variable name or method call with an implicit receiver. -static void name(Compiler* compiler, bool canAssign) -{ - // Look for the name in the scope chain up to the nearest enclosing method. - Token* token = &compiler->parser->previous; - - Variable variable = resolveNonmodule(compiler, token->start, token->length); - if (variable.index != -1) - { - bareName(compiler, canAssign, variable); - return; - } - - // TODO: The fact that we return above here if the variable is known and parse - // an optional argument list below if not means that the grammar is not - // context-free. A line of code in a method like "someName(foo)" is a parse - // error if "someName" is a defined variable in the surrounding scope and not - // if it isn't. Fix this. One option is to have "someName(foo)" always - // resolve to a self-call if there is an argument list, but that makes - // getters a little confusing. - - // If we're inside a method and the name is lowercase, treat it as a method - // on this. - if (wrenIsLocalName(token->start) && getEnclosingClass(compiler) != NULL) - { - loadThis(compiler); - namedCall(compiler, canAssign, CODE_CALL_0); - return; - } - - // Otherwise, look for a module-level variable with the name. - variable.scope = SCOPE_MODULE; - variable.index = wrenSymbolTableFind(&compiler->parser->module->variableNames, - token->start, token->length); - if (variable.index == -1) - { - // Implicitly define a module-level variable in - // the hopes that we get a real definition later. - variable.index = wrenDeclareVariable(compiler->parser->vm, - compiler->parser->module, - token->start, token->length, - token->line); - - if (variable.index == -2) - { - error(compiler, "Too many module variables defined."); - } - } - - bareName(compiler, canAssign, variable); -} - -static void null(Compiler* compiler, bool canAssign) -{ - emitOp(compiler, CODE_NULL); -} - -// A number or string literal. -static void literal(Compiler* compiler, bool canAssign) -{ - emitConstant(compiler, compiler->parser->previous.value); -} - -// A string literal that contains interpolated expressions. -// -// Interpolation is syntactic sugar for calling ".join()" on a list. So the -// string: -// -// "a %(b + c) d" -// -// is compiled roughly like: -// -// ["a ", b + c, " d"].join() -static void stringInterpolation(Compiler* compiler, bool canAssign) -{ - // Instantiate a new list. - loadCoreVariable(compiler, "List"); - callMethod(compiler, 0, "new()", 5); - - do - { - // The opening string part. - literal(compiler, false); - callMethod(compiler, 1, "addCore_(_)", 11); - - // The interpolated expression. - ignoreNewlines(compiler); - expression(compiler); - callMethod(compiler, 1, "addCore_(_)", 11); - - ignoreNewlines(compiler); - } while (match(compiler, TOKEN_INTERPOLATION)); - - // The trailing string part. - consume(compiler, TOKEN_STRING, "Expect end of string interpolation."); - literal(compiler, false); - callMethod(compiler, 1, "addCore_(_)", 11); - - // The list of interpolated parts. - callMethod(compiler, 0, "join()", 6); -} - -static void super_(Compiler* compiler, bool canAssign) -{ - ClassInfo* enclosingClass = getEnclosingClass(compiler); - if (enclosingClass == NULL) - { - error(compiler, "Cannot use 'super' outside of a method."); - } - - loadThis(compiler); - - // TODO: Super operator calls. - // TODO: There's no syntax for invoking a superclass constructor with a - // different name from the enclosing one. Figure that out. - - // See if it's a named super call, or an unnamed one. - if (match(compiler, TOKEN_DOT)) - { - // Compile the superclass call. - consume(compiler, TOKEN_NAME, "Expect method name after 'super.'."); - namedCall(compiler, canAssign, CODE_SUPER_0); - } - else if (enclosingClass != NULL) - { - // No explicit name, so use the name of the enclosing method. Make sure we - // check that enclosingClass isn't NULL first. We've already reported the - // error, but we don't want to crash here. - methodCall(compiler, CODE_SUPER_0, enclosingClass->signature); - } -} - -static void this_(Compiler* compiler, bool canAssign) -{ - if (getEnclosingClass(compiler) == NULL) - { - error(compiler, "Cannot use 'this' outside of a method."); - return; - } - - loadThis(compiler); -} - -// Subscript or "array indexing" operator like `foo[bar]`. -static void subscript(Compiler* compiler, bool canAssign) -{ - Signature signature = { "", 0, SIG_SUBSCRIPT, 0 }; - - // Parse the argument list. - finishArgumentList(compiler, &signature); - consume(compiler, TOKEN_RIGHT_BRACKET, "Expect ']' after arguments."); - - allowLineBeforeDot(compiler); - - if (canAssign && match(compiler, TOKEN_EQ)) - { - signature.type = SIG_SUBSCRIPT_SETTER; - - // Compile the assigned value. - validateNumParameters(compiler, ++signature.arity); - expression(compiler); - } - - callSignature(compiler, CODE_CALL_0, &signature); -} - -static void call(Compiler* compiler, bool canAssign) -{ - ignoreNewlines(compiler); - consume(compiler, TOKEN_NAME, "Expect method name after '.'."); - namedCall(compiler, canAssign, CODE_CALL_0); -} - -static void and_(Compiler* compiler, bool canAssign) -{ - ignoreNewlines(compiler); - - // Skip the right argument if the left is false. - int jump = emitJump(compiler, CODE_AND); - parsePrecedence(compiler, PREC_LOGICAL_AND); - patchJump(compiler, jump); -} - -static void or_(Compiler* compiler, bool canAssign) -{ - ignoreNewlines(compiler); - - // Skip the right argument if the left is true. - int jump = emitJump(compiler, CODE_OR); - parsePrecedence(compiler, PREC_LOGICAL_OR); - patchJump(compiler, jump); -} - -static void conditional(Compiler* compiler, bool canAssign) -{ - // Ignore newline after '?'. - ignoreNewlines(compiler); - - // Jump to the else branch if the condition is false. - int ifJump = emitJump(compiler, CODE_JUMP_IF); - - // Compile the then branch. - parsePrecedence(compiler, PREC_CONDITIONAL); - - consume(compiler, TOKEN_COLON, - "Expect ':' after then branch of conditional operator."); - ignoreNewlines(compiler); - - // Jump over the else branch when the if branch is taken. - int elseJump = emitJump(compiler, CODE_JUMP); - - // Compile the else branch. - patchJump(compiler, ifJump); - - parsePrecedence(compiler, PREC_ASSIGNMENT); - - // Patch the jump over the else. - patchJump(compiler, elseJump); -} - -void infixOp(Compiler* compiler, bool canAssign) -{ - GrammarRule* rule = getRule(compiler->parser->previous.type); - - // An infix operator cannot end an expression. - ignoreNewlines(compiler); - - // Compile the right-hand side. - parsePrecedence(compiler, (Precedence)(rule->precedence + 1)); - - // Call the operator method on the left-hand side. - Signature signature = { rule->name, (int)strlen(rule->name), SIG_METHOD, 1 }; - callSignature(compiler, CODE_CALL_0, &signature); -} - -// Compiles a method signature for an infix operator. -void infixSignature(Compiler* compiler, Signature* signature) -{ - // Add the RHS parameter. - signature->type = SIG_METHOD; - signature->arity = 1; - - // Parse the parameter name. - consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after operator name."); - declareNamedVariable(compiler); - consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameter name."); -} - -// Compiles a method signature for an unary operator (i.e. "!"). -void unarySignature(Compiler* compiler, Signature* signature) -{ - // Do nothing. The name is already complete. - signature->type = SIG_GETTER; -} - -// Compiles a method signature for an operator that can either be unary or -// infix (i.e. "-"). -void mixedSignature(Compiler* compiler, Signature* signature) -{ - signature->type = SIG_GETTER; - - // If there is a parameter, it's an infix operator, otherwise it's unary. - if (match(compiler, TOKEN_LEFT_PAREN)) - { - // Add the RHS parameter. - signature->type = SIG_METHOD; - signature->arity = 1; - - // Parse the parameter name. - declareNamedVariable(compiler); - consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameter name."); - } -} - -// Compiles an optional setter parameter in a method [signature]. -// -// Returns `true` if it was a setter. -static bool maybeSetter(Compiler* compiler, Signature* signature) -{ - // See if it's a setter. - if (!match(compiler, TOKEN_EQ)) return false; - - // It's a setter. - if (signature->type == SIG_SUBSCRIPT) - { - signature->type = SIG_SUBSCRIPT_SETTER; - } - else - { - signature->type = SIG_SETTER; - } - - // Parse the value parameter. - consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after '='."); - declareNamedVariable(compiler); - consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameter name."); - - signature->arity++; - - return true; -} - -// Compiles a method signature for a subscript operator. -void subscriptSignature(Compiler* compiler, Signature* signature) -{ - signature->type = SIG_SUBSCRIPT; - - // The signature currently has "[" as its name since that was the token that - // matched it. Clear that out. - signature->length = 0; - - // Parse the parameters inside the subscript. - finishParameterList(compiler, signature); - consume(compiler, TOKEN_RIGHT_BRACKET, "Expect ']' after parameters."); - - maybeSetter(compiler, signature); -} - -// Parses an optional parenthesized parameter list. Updates `type` and `arity` -// in [signature] to match what was parsed. -static void parameterList(Compiler* compiler, Signature* signature) -{ - // The parameter list is optional. - if (!match(compiler, TOKEN_LEFT_PAREN)) return; - - signature->type = SIG_METHOD; - - // Allow new line before an empty argument list - ignoreNewlines(compiler); - - // Allow an empty parameter list. - if (match(compiler, TOKEN_RIGHT_PAREN)) return; - - finishParameterList(compiler, signature); - consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameters."); -} - -// Compiles a method signature for a named method or setter. -void namedSignature(Compiler* compiler, Signature* signature) -{ - signature->type = SIG_GETTER; - - // If it's a setter, it can't also have a parameter list. - if (maybeSetter(compiler, signature)) return; - - // Regular named method with an optional parameter list. - parameterList(compiler, signature); -} - -// Compiles a method signature for a constructor. -void constructorSignature(Compiler* compiler, Signature* signature) -{ - consume(compiler, TOKEN_NAME, "Expect constructor name after 'construct'."); - - // Capture the name. - *signature = signatureFromToken(compiler, SIG_INITIALIZER); - - if (match(compiler, TOKEN_EQ)) - { - error(compiler, "A constructor cannot be a setter."); - } - - if (!match(compiler, TOKEN_LEFT_PAREN)) - { - error(compiler, "A constructor cannot be a getter."); - return; - } - - // Allow an empty parameter list. - if (match(compiler, TOKEN_RIGHT_PAREN)) return; - - finishParameterList(compiler, signature); - consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameters."); -} - -// This table defines all of the parsing rules for the prefix and infix -// expressions in the grammar. Expressions are parsed using a Pratt parser. -// -// See: http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/ -#define UNUSED { NULL, NULL, NULL, PREC_NONE, NULL } -#define PREFIX(fn) { fn, NULL, NULL, PREC_NONE, NULL } -#define INFIX(prec, fn) { NULL, fn, NULL, prec, NULL } -#define INFIX_OPERATOR(prec, name) { NULL, infixOp, infixSignature, prec, name } -#define PREFIX_OPERATOR(name) { unaryOp, NULL, unarySignature, PREC_NONE, name } -#define OPERATOR(name) { unaryOp, infixOp, mixedSignature, PREC_TERM, name } - -GrammarRule rules[] = -{ - /* TOKEN_LEFT_PAREN */ PREFIX(grouping), - /* TOKEN_RIGHT_PAREN */ UNUSED, - /* TOKEN_LEFT_BRACKET */ { list, subscript, subscriptSignature, PREC_CALL, NULL }, - /* TOKEN_RIGHT_BRACKET */ UNUSED, - /* TOKEN_LEFT_BRACE */ PREFIX(map), - /* TOKEN_RIGHT_BRACE */ UNUSED, - /* TOKEN_COLON */ UNUSED, - /* TOKEN_DOT */ INFIX(PREC_CALL, call), - /* TOKEN_DOTDOT */ INFIX_OPERATOR(PREC_RANGE, ".."), - /* TOKEN_DOTDOTDOT */ INFIX_OPERATOR(PREC_RANGE, "..."), - /* TOKEN_COMMA */ UNUSED, - /* TOKEN_STAR */ INFIX_OPERATOR(PREC_FACTOR, "*"), - /* TOKEN_SLASH */ INFIX_OPERATOR(PREC_FACTOR, "/"), - /* TOKEN_PERCENT */ INFIX_OPERATOR(PREC_FACTOR, "%"), - /* TOKEN_HASH */ UNUSED, - /* TOKEN_PLUS */ INFIX_OPERATOR(PREC_TERM, "+"), - /* TOKEN_MINUS */ OPERATOR("-"), - /* TOKEN_LTLT */ INFIX_OPERATOR(PREC_BITWISE_SHIFT, "<<"), - /* TOKEN_GTGT */ INFIX_OPERATOR(PREC_BITWISE_SHIFT, ">>"), - /* TOKEN_PIPE */ INFIX_OPERATOR(PREC_BITWISE_OR, "|"), - /* TOKEN_PIPEPIPE */ INFIX(PREC_LOGICAL_OR, or_), - /* TOKEN_CARET */ INFIX_OPERATOR(PREC_BITWISE_XOR, "^"), - /* TOKEN_AMP */ INFIX_OPERATOR(PREC_BITWISE_AND, "&"), - /* TOKEN_AMPAMP */ INFIX(PREC_LOGICAL_AND, and_), - /* TOKEN_BANG */ PREFIX_OPERATOR("!"), - /* TOKEN_TILDE */ PREFIX_OPERATOR("~"), - /* TOKEN_QUESTION */ INFIX(PREC_ASSIGNMENT, conditional), - /* TOKEN_EQ */ UNUSED, - /* TOKEN_LT */ INFIX_OPERATOR(PREC_COMPARISON, "<"), - /* TOKEN_GT */ INFIX_OPERATOR(PREC_COMPARISON, ">"), - /* TOKEN_LTEQ */ INFIX_OPERATOR(PREC_COMPARISON, "<="), - /* TOKEN_GTEQ */ INFIX_OPERATOR(PREC_COMPARISON, ">="), - /* TOKEN_EQEQ */ INFIX_OPERATOR(PREC_EQUALITY, "=="), - /* TOKEN_BANGEQ */ INFIX_OPERATOR(PREC_EQUALITY, "!="), - /* TOKEN_BREAK */ UNUSED, - /* TOKEN_CONTINUE */ UNUSED, - /* TOKEN_CLASS */ UNUSED, - /* TOKEN_CONSTRUCT */ { NULL, NULL, constructorSignature, PREC_NONE, NULL }, - /* TOKEN_ELSE */ UNUSED, - /* TOKEN_FALSE */ PREFIX(boolean), - /* TOKEN_FOR */ UNUSED, - /* TOKEN_FOREIGN */ UNUSED, - /* TOKEN_IF */ UNUSED, - /* TOKEN_IMPORT */ UNUSED, - /* TOKEN_AS */ UNUSED, - /* TOKEN_IN */ UNUSED, - /* TOKEN_IS */ INFIX_OPERATOR(PREC_IS, "is"), - /* TOKEN_NULL */ PREFIX(null), - /* TOKEN_RETURN */ UNUSED, - /* TOKEN_STATIC */ UNUSED, - /* TOKEN_SUPER */ PREFIX(super_), - /* TOKEN_THIS */ PREFIX(this_), - /* TOKEN_TRUE */ PREFIX(boolean), - /* TOKEN_VAR */ UNUSED, - /* TOKEN_WHILE */ UNUSED, - /* TOKEN_FIELD */ PREFIX(field), - /* TOKEN_STATIC_FIELD */ PREFIX(staticField), - /* TOKEN_NAME */ { name, NULL, namedSignature, PREC_NONE, NULL }, - /* TOKEN_NUMBER */ PREFIX(literal), - /* TOKEN_STRING */ PREFIX(literal), - /* TOKEN_INTERPOLATION */ PREFIX(stringInterpolation), - /* TOKEN_LINE */ UNUSED, - /* TOKEN_ERROR */ UNUSED, - /* TOKEN_EOF */ UNUSED -}; - -// Gets the [GrammarRule] associated with tokens of [type]. -static GrammarRule* getRule(TokenType type) -{ - return &rules[type]; -} - -// The main entrypoint for the top-down operator precedence parser. -void parsePrecedence(Compiler* compiler, Precedence precedence) -{ - nextToken(compiler->parser); - GrammarFn prefix = rules[compiler->parser->previous.type].prefix; - - if (prefix == NULL) - { - error(compiler, "Expected expression."); - return; - } - - // Track if the precendence of the surrounding expression is low enough to - // allow an assignment inside this one. We can't compile an assignment like - // a normal expression because it requires us to handle the LHS specially -- - // it needs to be an lvalue, not an rvalue. So, for each of the kinds of - // expressions that are valid lvalues -- names, subscripts, fields, etc. -- - // we pass in whether or not it appears in a context loose enough to allow - // "=". If so, it will parse the "=" itself and handle it appropriately. - bool canAssign = precedence <= PREC_CONDITIONAL; - prefix(compiler, canAssign); - - while (precedence <= rules[compiler->parser->current.type].precedence) - { - nextToken(compiler->parser); - GrammarFn infix = rules[compiler->parser->previous.type].infix; - infix(compiler, canAssign); - } -} - -// Parses an expression. Unlike statements, expressions leave a resulting value -// on the stack. -void expression(Compiler* compiler) -{ - parsePrecedence(compiler, PREC_LOWEST); -} - -// Returns the number of bytes for the arguments to the instruction -// at [ip] in [fn]'s bytecode. -static int getByteCountForArguments(const uint8_t* bytecode, - const Value* constants, int ip) -{ - Code instruction = (Code)bytecode[ip]; - switch (instruction) - { - case CODE_NULL: - case CODE_FALSE: - case CODE_TRUE: - case CODE_POP: - case CODE_CLOSE_UPVALUE: - case CODE_RETURN: - case CODE_END: - case CODE_LOAD_LOCAL_0: - case CODE_LOAD_LOCAL_1: - case CODE_LOAD_LOCAL_2: - case CODE_LOAD_LOCAL_3: - case CODE_LOAD_LOCAL_4: - case CODE_LOAD_LOCAL_5: - case CODE_LOAD_LOCAL_6: - case CODE_LOAD_LOCAL_7: - case CODE_LOAD_LOCAL_8: - case CODE_CONSTRUCT: - case CODE_FOREIGN_CONSTRUCT: - case CODE_FOREIGN_CLASS: - case CODE_END_MODULE: - case CODE_END_CLASS: - return 0; - - case CODE_LOAD_LOCAL: - case CODE_STORE_LOCAL: - case CODE_LOAD_UPVALUE: - case CODE_STORE_UPVALUE: - case CODE_LOAD_FIELD_THIS: - case CODE_STORE_FIELD_THIS: - case CODE_LOAD_FIELD: - case CODE_STORE_FIELD: - case CODE_CLASS: - return 1; - - case CODE_CONSTANT: - case CODE_LOAD_MODULE_VAR: - case CODE_STORE_MODULE_VAR: - case CODE_CALL_0: - case CODE_CALL_1: - case CODE_CALL_2: - case CODE_CALL_3: - case CODE_CALL_4: - case CODE_CALL_5: - case CODE_CALL_6: - case CODE_CALL_7: - case CODE_CALL_8: - case CODE_CALL_9: - case CODE_CALL_10: - case CODE_CALL_11: - case CODE_CALL_12: - case CODE_CALL_13: - case CODE_CALL_14: - case CODE_CALL_15: - case CODE_CALL_16: - case CODE_JUMP: - case CODE_LOOP: - case CODE_JUMP_IF: - case CODE_AND: - case CODE_OR: - case CODE_METHOD_INSTANCE: - case CODE_METHOD_STATIC: - case CODE_IMPORT_MODULE: - case CODE_IMPORT_VARIABLE: - return 2; - - case CODE_SUPER_0: - case CODE_SUPER_1: - case CODE_SUPER_2: - case CODE_SUPER_3: - case CODE_SUPER_4: - case CODE_SUPER_5: - case CODE_SUPER_6: - case CODE_SUPER_7: - case CODE_SUPER_8: - case CODE_SUPER_9: - case CODE_SUPER_10: - case CODE_SUPER_11: - case CODE_SUPER_12: - case CODE_SUPER_13: - case CODE_SUPER_14: - case CODE_SUPER_15: - case CODE_SUPER_16: - return 4; - - case CODE_CLOSURE: - { - int constant = (bytecode[ip + 1] << 8) | bytecode[ip + 2]; - ObjFn* loadedFn = AS_FN(constants[constant]); - - // There are two bytes for the constant, then two for each upvalue. - return 2 + (loadedFn->numUpvalues * 2); - } - } - - UNREACHABLE(); - return 0; -} - -// Marks the beginning of a loop. Keeps track of the current instruction so we -// know what to loop back to at the end of the body. -static void startLoop(Compiler* compiler, Loop* loop) -{ - loop->enclosing = compiler->loop; - loop->start = compiler->fn->code.count - 1; - loop->scopeDepth = compiler->scopeDepth; - compiler->loop = loop; -} - -// Emits the [CODE_JUMP_IF] instruction used to test the loop condition and -// potentially exit the loop. Keeps track of the instruction so we can patch it -// later once we know where the end of the body is. -static void testExitLoop(Compiler* compiler) -{ - compiler->loop->exitJump = emitJump(compiler, CODE_JUMP_IF); -} - -// Compiles the body of the loop and tracks its extent so that contained "break" -// statements can be handled correctly. -static void loopBody(Compiler* compiler) -{ - compiler->loop->body = compiler->fn->code.count; - statement(compiler); -} - -// Ends the current innermost loop. Patches up all jumps and breaks now that -// we know where the end of the loop is. -static void endLoop(Compiler* compiler) -{ - // We don't check for overflow here since the forward jump over the loop body - // will report an error for the same problem. - int loopOffset = compiler->fn->code.count - compiler->loop->start + 2; - emitShortArg(compiler, CODE_LOOP, loopOffset); - - patchJump(compiler, compiler->loop->exitJump); - - // Find any break placeholder instructions (which will be CODE_END in the - // bytecode) and replace them with real jumps. - int i = compiler->loop->body; - while (i < compiler->fn->code.count) - { - if (compiler->fn->code.data[i] == CODE_END) - { - compiler->fn->code.data[i] = CODE_JUMP; - patchJump(compiler, i + 1); - i += 3; - } - else - { - // Skip this instruction and its arguments. - i += 1 + getByteCountForArguments(compiler->fn->code.data, - compiler->fn->constants.data, i); - } - } - - compiler->loop = compiler->loop->enclosing; -} - -static void forStatement(Compiler* compiler) -{ - // A for statement like: - // - // for (i in sequence.expression) { - // System.print(i) - // } - // - // Is compiled to bytecode almost as if the source looked like this: - // - // { - // var seq_ = sequence.expression - // var iter_ - // while (iter_ = seq_.iterate(iter_)) { - // var i = seq_.iteratorValue(iter_) - // System.print(i) - // } - // } - // - // It's not exactly this, because the synthetic variables `seq_` and `iter_` - // actually get names that aren't valid Wren identfiers, but that's the basic - // idea. - // - // The important parts are: - // - The sequence expression is only evaluated once. - // - The .iterate() method is used to advance the iterator and determine if - // it should exit the loop. - // - The .iteratorValue() method is used to get the value at the current - // iterator position. - - // Create a scope for the hidden local variables used for the iterator. - pushScope(compiler); - - consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'for'."); - consume(compiler, TOKEN_NAME, "Expect for loop variable name."); - - // Remember the name of the loop variable. - const char* name = compiler->parser->previous.start; - int length = compiler->parser->previous.length; - - consume(compiler, TOKEN_IN, "Expect 'in' after loop variable."); - ignoreNewlines(compiler); - - // Evaluate the sequence expression and store it in a hidden local variable. - // The space in the variable name ensures it won't collide with a user-defined - // variable. - expression(compiler); - - // Verify that there is space to hidden local variables. - // Note that we expect only two addLocal calls next to each other in the - // following code. - if (compiler->numLocals + 2 > MAX_LOCALS) - { - error(compiler, "Cannot declare more than %d variables in one scope. (Not enough space for for-loops internal variables)", - MAX_LOCALS); - return; - } - int seqSlot = addLocal(compiler, "seq ", 4); - - // Create another hidden local for the iterator object. - null(compiler, false); - int iterSlot = addLocal(compiler, "iter ", 5); - - consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after loop expression."); - - Loop loop; - startLoop(compiler, &loop); - - // Advance the iterator by calling the ".iterate" method on the sequence. - loadLocal(compiler, seqSlot); - loadLocal(compiler, iterSlot); - - // Update and test the iterator. - callMethod(compiler, 1, "iterate(_)", 10); - emitByteArg(compiler, CODE_STORE_LOCAL, iterSlot); - testExitLoop(compiler); - - // Get the current value in the sequence by calling ".iteratorValue". - loadLocal(compiler, seqSlot); - loadLocal(compiler, iterSlot); - callMethod(compiler, 1, "iteratorValue(_)", 16); - - // Bind the loop variable in its own scope. This ensures we get a fresh - // variable each iteration so that closures for it don't all see the same one. - pushScope(compiler); - addLocal(compiler, name, length); - - loopBody(compiler); - - // Loop variable. - popScope(compiler); - - endLoop(compiler); - - // Hidden variables. - popScope(compiler); -} - -static void ifStatement(Compiler* compiler) -{ - // Compile the condition. - consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'if'."); - expression(compiler); - consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after if condition."); - - // Jump to the else branch if the condition is false. - int ifJump = emitJump(compiler, CODE_JUMP_IF); - - // Compile the then branch. - statement(compiler); - - // Compile the else branch if there is one. - if (match(compiler, TOKEN_ELSE)) - { - // Jump over the else branch when the if branch is taken. - int elseJump = emitJump(compiler, CODE_JUMP); - patchJump(compiler, ifJump); - - statement(compiler); - - // Patch the jump over the else. - patchJump(compiler, elseJump); - } - else - { - patchJump(compiler, ifJump); - } -} - -static void whileStatement(Compiler* compiler) -{ - Loop loop; - startLoop(compiler, &loop); - - // Compile the condition. - consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'while'."); - expression(compiler); - consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after while condition."); - - testExitLoop(compiler); - loopBody(compiler); - endLoop(compiler); -} - -// Compiles a simple statement. These can only appear at the top-level or -// within curly blocks. Simple statements exclude variable binding statements -// like "var" and "class" which are not allowed directly in places like the -// branches of an "if" statement. -// -// Unlike expressions, statements do not leave a value on the stack. -void statement(Compiler* compiler) -{ - if (match(compiler, TOKEN_BREAK)) - { - if (compiler->loop == NULL) - { - error(compiler, "Cannot use 'break' outside of a loop."); - return; - } - - // Since we will be jumping out of the scope, make sure any locals in it - // are discarded first. - discardLocals(compiler, compiler->loop->scopeDepth + 1); - - // Emit a placeholder instruction for the jump to the end of the body. When - // we're done compiling the loop body and know where the end is, we'll - // replace these with `CODE_JUMP` instructions with appropriate offsets. - // We use `CODE_END` here because that can't occur in the middle of - // bytecode. - emitJump(compiler, CODE_END); - } - else if (match(compiler, TOKEN_CONTINUE)) - { - if (compiler->loop == NULL) - { - error(compiler, "Cannot use 'continue' outside of a loop."); - return; - } - - // Since we will be jumping out of the scope, make sure any locals in it - // are discarded first. - discardLocals(compiler, compiler->loop->scopeDepth + 1); - - // emit a jump back to the top of the loop - int loopOffset = compiler->fn->code.count - compiler->loop->start + 2; - emitShortArg(compiler, CODE_LOOP, loopOffset); - } - else if (match(compiler, TOKEN_FOR)) - { - forStatement(compiler); - } - else if (match(compiler, TOKEN_IF)) - { - ifStatement(compiler); - } - else if (match(compiler, TOKEN_RETURN)) - { - // Compile the return value. - if (peek(compiler) == TOKEN_LINE) - { - // If there's no expression after return, initializers should - // return 'this' and regular methods should return null - Code result = compiler->isInitializer ? CODE_LOAD_LOCAL_0 : CODE_NULL; - emitOp(compiler, result); - } - else - { - if (compiler->isInitializer) - { - error(compiler, "A constructor cannot return a value."); - } - - expression(compiler); - } - - emitOp(compiler, CODE_RETURN); - } - else if (match(compiler, TOKEN_WHILE)) - { - whileStatement(compiler); - } - else if (match(compiler, TOKEN_LEFT_BRACE)) - { - // Block statement. - pushScope(compiler); - if (finishBlock(compiler)) - { - // Block was an expression, so discard it. - emitOp(compiler, CODE_POP); - } - popScope(compiler); - } - else - { - // Expression statement. - expression(compiler); - emitOp(compiler, CODE_POP); - } -} - -// Creates a matching constructor method for an initializer with [signature] -// and [initializerSymbol]. -// -// Construction is a two-stage process in Wren that involves two separate -// methods. There is a static method that allocates a new instance of the class. -// It then invokes an initializer method on the new instance, forwarding all of -// the constructor arguments to it. -// -// The allocator method always has a fixed implementation: -// -// CODE_CONSTRUCT - Replace the class in slot 0 with a new instance of it. -// CODE_CALL - Invoke the initializer on the new instance. -// -// This creates that method and calls the initializer with [initializerSymbol]. -static void createConstructor(Compiler* compiler, Signature* signature, - int initializerSymbol) -{ - Compiler methodCompiler; - initCompiler(&methodCompiler, compiler->parser, compiler, true); - - // Allocate the instance. - emitOp(&methodCompiler, compiler->enclosingClass->isForeign - ? CODE_FOREIGN_CONSTRUCT : CODE_CONSTRUCT); - - // Run its initializer. - emitShortArg(&methodCompiler, (Code)(CODE_CALL_0 + signature->arity), - initializerSymbol); - - // Return the instance. - emitOp(&methodCompiler, CODE_RETURN); - - endCompiler(&methodCompiler, "", 0); -} - -// Loads the enclosing class onto the stack and then binds the function already -// on the stack as a method on that class. -static void defineMethod(Compiler* compiler, Variable classVariable, - bool isStatic, int methodSymbol) -{ - // Load the class. We have to do this for each method because we can't - // keep the class on top of the stack. If there are static fields, they - // will be locals above the initial variable slot for the class on the - // stack. To skip past those, we just load the class each time right before - // defining a method. - loadVariable(compiler, classVariable); - - // Define the method. - Code instruction = isStatic ? CODE_METHOD_STATIC : CODE_METHOD_INSTANCE; - emitShortArg(compiler, instruction, methodSymbol); -} - -// Declares a method in the enclosing class with [signature]. -// -// Reports an error if a method with that signature is already declared. -// Returns the symbol for the method. -static int declareMethod(Compiler* compiler, Signature* signature, - const char* name, int length) -{ - int symbol = signatureSymbol(compiler, signature); - - // See if the class has already declared method with this signature. - ClassInfo* classInfo = compiler->enclosingClass; - IntBuffer* methods = classInfo->inStatic - ? &classInfo->staticMethods : &classInfo->methods; - for (int i = 0; i < methods->count; i++) - { - if (methods->data[i] == symbol) - { - const char* staticPrefix = classInfo->inStatic ? "static " : ""; - error(compiler, "Class %s already defines a %smethod '%s'.", - &compiler->enclosingClass->name->value, staticPrefix, name); - break; - } - } - - wrenIntBufferWrite(compiler->parser->vm, methods, symbol); - return symbol; -} - -static Value consumeLiteral(Compiler* compiler, const char* message) -{ - if(match(compiler, TOKEN_FALSE)) return FALSE_VAL; - if(match(compiler, TOKEN_TRUE)) return TRUE_VAL; - if(match(compiler, TOKEN_NUMBER)) return compiler->parser->previous.value; - if(match(compiler, TOKEN_STRING)) return compiler->parser->previous.value; - if(match(compiler, TOKEN_NAME)) return compiler->parser->previous.value; - - error(compiler, message); - nextToken(compiler->parser); - return NULL_VAL; -} - -static bool matchAttribute(Compiler* compiler) { - - if(match(compiler, TOKEN_HASH)) - { - compiler->numAttributes++; - bool runtimeAccess = match(compiler, TOKEN_BANG); - if(match(compiler, TOKEN_NAME)) - { - Value group = compiler->parser->previous.value; - TokenType ahead = peek(compiler); - if(ahead == TOKEN_EQ || ahead == TOKEN_LINE) - { - Value key = group; - Value value = NULL_VAL; - if(match(compiler, TOKEN_EQ)) - { - value = consumeLiteral(compiler, "Expect a Bool, Num, String or Identifier literal for an attribute value."); - } - if(runtimeAccess) addToAttributeGroup(compiler, NULL_VAL, key, value); - } - else if(match(compiler, TOKEN_LEFT_PAREN)) - { - ignoreNewlines(compiler); - if(match(compiler, TOKEN_RIGHT_PAREN)) - { - error(compiler, "Expected attributes in group, group cannot be empty."); - } - else - { - while(peek(compiler) != TOKEN_RIGHT_PAREN) - { - consume(compiler, TOKEN_NAME, "Expect name for attribute key."); - Value key = compiler->parser->previous.value; - Value value = NULL_VAL; - if(match(compiler, TOKEN_EQ)) - { - value = consumeLiteral(compiler, "Expect a Bool, Num, String or Identifier literal for an attribute value."); - } - if(runtimeAccess) addToAttributeGroup(compiler, group, key, value); - ignoreNewlines(compiler); - if(!match(compiler, TOKEN_COMMA)) break; - ignoreNewlines(compiler); - } - - ignoreNewlines(compiler); - consume(compiler, TOKEN_RIGHT_PAREN, - "Expected ')' after grouped attributes."); - } - } - else - { - error(compiler, "Expect an equal, newline or grouping after an attribute key."); - } - } - else - { - error(compiler, "Expect an attribute definition after #."); - } - - consumeLine(compiler, "Expect newline after attribute."); - return true; - } - - return false; -} - -// Compiles a method definition inside a class body. -// -// Returns `true` if it compiled successfully, or `false` if the method couldn't -// be parsed. -static bool method(Compiler* compiler, Variable classVariable) -{ - // Parse any attributes before the method and store them - if(matchAttribute(compiler)) { - return method(compiler, classVariable); - } - - // TODO: What about foreign constructors? - bool isForeign = match(compiler, TOKEN_FOREIGN); - bool isStatic = match(compiler, TOKEN_STATIC); - compiler->enclosingClass->inStatic = isStatic; - - SignatureFn signatureFn = rules[compiler->parser->current.type].method; - nextToken(compiler->parser); - - if (signatureFn == NULL) - { - error(compiler, "Expect method definition."); - return false; - } - - // Build the method signature. - Signature signature = signatureFromToken(compiler, SIG_GETTER); - compiler->enclosingClass->signature = &signature; - - Compiler methodCompiler; - initCompiler(&methodCompiler, compiler->parser, compiler, true); - - // Compile the method signature. - signatureFn(&methodCompiler, &signature); - - methodCompiler.isInitializer = signature.type == SIG_INITIALIZER; - - if (isStatic && signature.type == SIG_INITIALIZER) - { - error(compiler, "A constructor cannot be static."); - } - - // Include the full signature in debug messages in stack traces. - char fullSignature[MAX_METHOD_SIGNATURE]; - int length; - signatureToString(&signature, fullSignature, &length); - - // Copy any attributes the compiler collected into the enclosing class - copyMethodAttributes(compiler, isForeign, isStatic, fullSignature, length); - - // Check for duplicate methods. Doesn't matter that it's already been - // defined, error will discard bytecode anyway. - // Check if the method table already contains this symbol - int methodSymbol = declareMethod(compiler, &signature, fullSignature, length); - - if (isForeign) - { - // Define a constant for the signature. - emitConstant(compiler, wrenNewStringLength(compiler->parser->vm, - fullSignature, length)); - - // We don't need the function we started compiling in the parameter list - // any more. - methodCompiler.parser->vm->compiler = methodCompiler.parent; - } - else - { - consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' to begin method body."); - finishBody(&methodCompiler); - endCompiler(&methodCompiler, fullSignature, length); - } - - // Define the method. For a constructor, this defines the instance - // initializer method. - defineMethod(compiler, classVariable, isStatic, methodSymbol); - - if (signature.type == SIG_INITIALIZER) - { - // Also define a matching constructor method on the metaclass. - signature.type = SIG_METHOD; - int constructorSymbol = signatureSymbol(compiler, &signature); - - createConstructor(compiler, &signature, methodSymbol); - defineMethod(compiler, classVariable, true, constructorSymbol); - } - - return true; -} - -// Compiles a class definition. Assumes the "class" token has already been -// consumed (along with a possibly preceding "foreign" token). -static void classDefinition(Compiler* compiler, bool isForeign) -{ - // Create a variable to store the class in. - Variable classVariable; - classVariable.scope = compiler->scopeDepth == -1 ? SCOPE_MODULE : SCOPE_LOCAL; - classVariable.index = declareNamedVariable(compiler); - - // Create shared class name value - Value classNameString = wrenNewStringLength(compiler->parser->vm, - compiler->parser->previous.start, compiler->parser->previous.length); - - // Create class name string to track method duplicates - ObjString* className = AS_STRING(classNameString); - - // Make a string constant for the name. - emitConstant(compiler, classNameString); - - // Load the superclass (if there is one). - if (match(compiler, TOKEN_IS)) - { - parsePrecedence(compiler, PREC_CALL); - } - else - { - // Implicitly inherit from Object. - loadCoreVariable(compiler, "Object"); - } - - // Store a placeholder for the number of fields argument. We don't know the - // count until we've compiled all the methods to see which fields are used. - int numFieldsInstruction = -1; - if (isForeign) - { - emitOp(compiler, CODE_FOREIGN_CLASS); - } - else - { - numFieldsInstruction = emitByteArg(compiler, CODE_CLASS, 255); - } - - // Store it in its name. - defineVariable(compiler, classVariable.index); - - // Push a local variable scope. Static fields in a class body are hoisted out - // into local variables declared in this scope. Methods that use them will - // have upvalues referencing them. - pushScope(compiler); - - ClassInfo classInfo; - classInfo.isForeign = isForeign; - classInfo.name = className; - - // Allocate attribute maps if necessary. - // A method will allocate the methods one if needed - classInfo.classAttributes = compiler->attributes->count > 0 - ? wrenNewMap(compiler->parser->vm) - : NULL; - classInfo.methodAttributes = NULL; - // Copy any existing attributes into the class - copyAttributes(compiler, classInfo.classAttributes); - - // Set up a symbol table for the class's fields. We'll initially compile - // them to slots starting at zero. When the method is bound to the class, the - // bytecode will be adjusted by [wrenBindMethod] to take inherited fields - // into account. - wrenSymbolTableInit(&classInfo.fields); - - // Set up symbol buffers to track duplicate static and instance methods. - wrenIntBufferInit(&classInfo.methods); - wrenIntBufferInit(&classInfo.staticMethods); - compiler->enclosingClass = &classInfo; - - // Compile the method definitions. - consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' after class declaration."); - matchLine(compiler); - - while (!match(compiler, TOKEN_RIGHT_BRACE)) - { - if (!method(compiler, classVariable)) break; - - // Don't require a newline after the last definition. - if (match(compiler, TOKEN_RIGHT_BRACE)) break; - - consumeLine(compiler, "Expect newline after definition in class."); - } - - // If any attributes are present, - // instantiate a ClassAttributes instance for the class - // and send it over to CODE_END_CLASS - bool hasAttr = classInfo.classAttributes != NULL || - classInfo.methodAttributes != NULL; - if(hasAttr) { - emitClassAttributes(compiler, &classInfo); - loadVariable(compiler, classVariable); - // At the moment, we don't have other uses for CODE_END_CLASS, - // so we put it inside this condition. Later, we can always - // emit it and use it as needed. - emitOp(compiler, CODE_END_CLASS); - } - - // Update the class with the number of fields. - if (!isForeign) - { - compiler->fn->code.data[numFieldsInstruction] = - (uint8_t)classInfo.fields.count; - } - - // Clear symbol tables for tracking field and method names. - wrenSymbolTableClear(compiler->parser->vm, &classInfo.fields); - wrenIntBufferClear(compiler->parser->vm, &classInfo.methods); - wrenIntBufferClear(compiler->parser->vm, &classInfo.staticMethods); - compiler->enclosingClass = NULL; - popScope(compiler); -} - -// Compiles an "import" statement. -// -// An import compiles to a series of instructions. Given: -// -// import "foo" for Bar, Baz -// -// We compile a single IMPORT_MODULE "foo" instruction to load the module -// itself. When that finishes executing the imported module, it leaves the -// ObjModule in vm->lastModule. Then, for Bar and Baz, we: -// -// * Declare a variable in the current scope with that name. -// * Emit an IMPORT_VARIABLE instruction to load the variable's value from the -// other module. -// * Compile the code to store that value in the variable in this scope. -static void import(Compiler* compiler) -{ - ignoreNewlines(compiler); - consume(compiler, TOKEN_STRING, "Expect a string after 'import'."); - int moduleConstant = addConstant(compiler, compiler->parser->previous.value); - - // Load the module. - emitShortArg(compiler, CODE_IMPORT_MODULE, moduleConstant); - - // Discard the unused result value from calling the module body's closure. - emitOp(compiler, CODE_POP); - - // The for clause is optional. - if (!match(compiler, TOKEN_FOR)) return; - - // Compile the comma-separated list of variables to import. - do - { - ignoreNewlines(compiler); - - consume(compiler, TOKEN_NAME, "Expect variable name."); - - // We need to hold onto the source variable, - // in order to reference it in the import later - Token sourceVariableToken = compiler->parser->previous; - - // Define a string constant for the original variable name. - int sourceVariableConstant = addConstant(compiler, - wrenNewStringLength(compiler->parser->vm, - sourceVariableToken.start, - sourceVariableToken.length)); - - // Store the symbol we care about for the variable - int slot = -1; - if(match(compiler, TOKEN_AS)) - { - //import "module" for Source as Dest - //Use 'Dest' as the name by declaring a new variable for it. - //This parses a name after the 'as' and defines it. - slot = declareNamedVariable(compiler); - } - else - { - //import "module" for Source - //Uses 'Source' as the name directly - slot = declareVariable(compiler, &sourceVariableToken); - } - - // Load the variable from the other module. - emitShortArg(compiler, CODE_IMPORT_VARIABLE, sourceVariableConstant); - - // Store the result in the variable here. - defineVariable(compiler, slot); - } while (match(compiler, TOKEN_COMMA)); -} - -// Compiles a "var" variable definition statement. -static void variableDefinition(Compiler* compiler) -{ - // Grab its name, but don't declare it yet. A (local) variable shouldn't be - // in scope in its own initializer. - consume(compiler, TOKEN_NAME, "Expect variable name."); - Token nameToken = compiler->parser->previous; - - // Compile the initializer. - if (match(compiler, TOKEN_EQ)) - { - ignoreNewlines(compiler); - expression(compiler); - } - else - { - // Default initialize it to null. - null(compiler, false); - } - - // Now put it in scope. - int symbol = declareVariable(compiler, &nameToken); - defineVariable(compiler, symbol); -} - -// Compiles a "definition". These are the statements that bind new variables. -// They can only appear at the top level of a block and are prohibited in places -// like the non-curly body of an if or while. -void definition(Compiler* compiler) -{ - if(matchAttribute(compiler)) { - definition(compiler); - return; - } - - if (match(compiler, TOKEN_CLASS)) - { - classDefinition(compiler, false); - return; - } - else if (match(compiler, TOKEN_FOREIGN)) - { - consume(compiler, TOKEN_CLASS, "Expect 'class' after 'foreign'."); - classDefinition(compiler, true); - return; - } - - disallowAttributes(compiler); - - if (match(compiler, TOKEN_IMPORT)) - { - import(compiler); - } - else if (match(compiler, TOKEN_VAR)) - { - variableDefinition(compiler); - } - else - { - statement(compiler); - } -} - -ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source, - bool isExpression, bool printErrors) -{ - // Skip the UTF-8 BOM if there is one. - if (strncmp(source, "\xEF\xBB\xBF", 3) == 0) source += 3; - - Parser parser; - parser.vm = vm; - parser.module = module; - parser.source = source; - - parser.tokenStart = source; - parser.currentChar = source; - parser.currentLine = 1; - parser.numParens = 0; - - // Zero-init the current token. This will get copied to previous when - // nextToken() is called below. - parser.next.type = TOKEN_ERROR; - parser.next.start = source; - parser.next.length = 0; - parser.next.line = 0; - parser.next.value = UNDEFINED_VAL; - - parser.printErrors = printErrors; - parser.hasError = false; - - // Read the first token into next - nextToken(&parser); - // Copy next -> current - nextToken(&parser); - - int numExistingVariables = module->variables.count; - - Compiler compiler; - initCompiler(&compiler, &parser, NULL, false); - ignoreNewlines(&compiler); - - if (isExpression) - { - expression(&compiler); - consume(&compiler, TOKEN_EOF, "Expect end of expression."); - } - else - { - while (!match(&compiler, TOKEN_EOF)) - { - definition(&compiler); - - // If there is no newline, it must be the end of file on the same line. - if (!matchLine(&compiler)) - { - consume(&compiler, TOKEN_EOF, "Expect end of file."); - break; - } - } - - emitOp(&compiler, CODE_END_MODULE); - } - - emitOp(&compiler, CODE_RETURN); - - // See if there are any implicitly declared module-level variables that never - // got an explicit definition. They will have values that are numbers - // indicating the line where the variable was first used. - for (int i = numExistingVariables; i < parser.module->variables.count; i++) - { - if (IS_NUM(parser.module->variables.data[i])) - { - // Synthesize a token for the original use site. - parser.previous.type = TOKEN_NAME; - parser.previous.start = parser.module->variableNames.data[i]->value; - parser.previous.length = parser.module->variableNames.data[i]->length; - parser.previous.line = (int)AS_NUM(parser.module->variables.data[i]); - error(&compiler, "Variable is used but not defined."); - } - } - - return endCompiler(&compiler, "(script)", 8); -} - -void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn) -{ - int ip = 0; - for (;;) - { - Code instruction = (Code)fn->code.data[ip]; - switch (instruction) - { - case CODE_LOAD_FIELD: - case CODE_STORE_FIELD: - case CODE_LOAD_FIELD_THIS: - case CODE_STORE_FIELD_THIS: - // Shift this class's fields down past the inherited ones. We don't - // check for overflow here because we'll see if the number of fields - // overflows when the subclass is created. - fn->code.data[ip + 1] += classObj->superclass->numFields; - break; - - case CODE_SUPER_0: - case CODE_SUPER_1: - case CODE_SUPER_2: - case CODE_SUPER_3: - case CODE_SUPER_4: - case CODE_SUPER_5: - case CODE_SUPER_6: - case CODE_SUPER_7: - case CODE_SUPER_8: - case CODE_SUPER_9: - case CODE_SUPER_10: - case CODE_SUPER_11: - case CODE_SUPER_12: - case CODE_SUPER_13: - case CODE_SUPER_14: - case CODE_SUPER_15: - case CODE_SUPER_16: - { - // Fill in the constant slot with a reference to the superclass. - int constant = (fn->code.data[ip + 3] << 8) | fn->code.data[ip + 4]; - fn->constants.data[constant] = OBJ_VAL(classObj->superclass); - break; - } - - case CODE_CLOSURE: - { - // Bind the nested closure too. - int constant = (fn->code.data[ip + 1] << 8) | fn->code.data[ip + 2]; - wrenBindMethodCode(classObj, AS_FN(fn->constants.data[constant])); - break; - } - - case CODE_END: - return; - - default: - // Other instructions are unaffected, so just skip over them. - break; - } - ip += 1 + getByteCountForArguments(fn->code.data, fn->constants.data, ip); - } -} - -void wrenMarkCompiler(WrenVM* vm, Compiler* compiler) -{ - wrenGrayValue(vm, compiler->parser->current.value); - wrenGrayValue(vm, compiler->parser->previous.value); - wrenGrayValue(vm, compiler->parser->next.value); - - // Walk up the parent chain to mark the outer compilers too. The VM only - // tracks the innermost one. - do - { - wrenGrayObj(vm, (Obj*)compiler->fn); - wrenGrayObj(vm, (Obj*)compiler->constants); - wrenGrayObj(vm, (Obj*)compiler->attributes); - - if (compiler->enclosingClass != NULL) - { - wrenBlackenSymbolTable(vm, &compiler->enclosingClass->fields); - - if(compiler->enclosingClass->methodAttributes != NULL) - { - wrenGrayObj(vm, (Obj*)compiler->enclosingClass->methodAttributes); - } - if(compiler->enclosingClass->classAttributes != NULL) - { - wrenGrayObj(vm, (Obj*)compiler->enclosingClass->classAttributes); - } - } - - compiler = compiler->parent; - } - while (compiler != NULL); -} - -// Helpers for Attributes - -// Throw an error if any attributes were found preceding, -// and clear the attributes so the error doesn't keep happening. -static void disallowAttributes(Compiler* compiler) -{ - if (compiler->numAttributes > 0) - { - error(compiler, "Attributes can only specified before a class or a method"); - wrenMapClear(compiler->parser->vm, compiler->attributes); - compiler->numAttributes = 0; - } -} - -// Add an attribute to a given group in the compiler attribues map -static void addToAttributeGroup(Compiler* compiler, - Value group, Value key, Value value) -{ - WrenVM* vm = compiler->parser->vm; - - if(IS_OBJ(group)) wrenPushRoot(vm, AS_OBJ(group)); - if(IS_OBJ(key)) wrenPushRoot(vm, AS_OBJ(key)); - if(IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value)); - - Value groupMapValue = wrenMapGet(compiler->attributes, group); - if(IS_UNDEFINED(groupMapValue)) - { - groupMapValue = OBJ_VAL(wrenNewMap(vm)); - wrenMapSet(vm, compiler->attributes, group, groupMapValue); - } - - //we store them as a map per so we can maintain duplicate keys - //group = { key:[value, ...], } - ObjMap* groupMap = AS_MAP(groupMapValue); - - //var keyItems = group[key] - //if(!keyItems) keyItems = group[key] = [] - Value keyItemsValue = wrenMapGet(groupMap, key); - if(IS_UNDEFINED(keyItemsValue)) - { - keyItemsValue = OBJ_VAL(wrenNewList(vm, 0)); - wrenMapSet(vm, groupMap, key, keyItemsValue); - } - - //keyItems.add(value) - ObjList* keyItems = AS_LIST(keyItemsValue); - wrenValueBufferWrite(vm, &keyItems->elements, value); - - if(IS_OBJ(group)) wrenPopRoot(vm); - if(IS_OBJ(key)) wrenPopRoot(vm); - if(IS_OBJ(value)) wrenPopRoot(vm); -} - - -// Emit the attributes in the give map onto the stack -static void emitAttributes(Compiler* compiler, ObjMap* attributes) -{ - // Instantiate a new map for the attributes - loadCoreVariable(compiler, "Map"); - callMethod(compiler, 0, "new()", 5); - - // The attributes are stored as group = { key:[value, value, ...] } - // so our first level is the group map - for(uint32_t groupIdx = 0; groupIdx < attributes->capacity; groupIdx++) - { - const MapEntry* groupEntry = &attributes->entries[groupIdx]; - if(IS_UNDEFINED(groupEntry->key)) continue; - //group key - emitConstant(compiler, groupEntry->key); - - //group value is gonna be a map - loadCoreVariable(compiler, "Map"); - callMethod(compiler, 0, "new()", 5); - - ObjMap* groupItems = AS_MAP(groupEntry->value); - for(uint32_t itemIdx = 0; itemIdx < groupItems->capacity; itemIdx++) - { - const MapEntry* itemEntry = &groupItems->entries[itemIdx]; - if(IS_UNDEFINED(itemEntry->key)) continue; - - emitConstant(compiler, itemEntry->key); - // Attribute key value, key = [] - loadCoreVariable(compiler, "List"); - callMethod(compiler, 0, "new()", 5); - // Add the items to the key list - ObjList* items = AS_LIST(itemEntry->value); - for(int itemIdx = 0; itemIdx < items->elements.count; ++itemIdx) - { - emitConstant(compiler, items->elements.data[itemIdx]); - callMethod(compiler, 1, "addCore_(_)", 11); - } - // Add the list to the map - callMethod(compiler, 2, "addCore_(_,_)", 13); - } - - // Add the key/value to the map - callMethod(compiler, 2, "addCore_(_,_)", 13); - } - -} - -// Methods are stored as method <-> attributes, so we have to have -// an indirection to resolve for methods -static void emitAttributeMethods(Compiler* compiler, ObjMap* attributes) -{ - // Instantiate a new map for the attributes - loadCoreVariable(compiler, "Map"); - callMethod(compiler, 0, "new()", 5); - - for(uint32_t methodIdx = 0; methodIdx < attributes->capacity; methodIdx++) - { - const MapEntry* methodEntry = &attributes->entries[methodIdx]; - if(IS_UNDEFINED(methodEntry->key)) continue; - emitConstant(compiler, methodEntry->key); - ObjMap* attributeMap = AS_MAP(methodEntry->value); - emitAttributes(compiler, attributeMap); - callMethod(compiler, 2, "addCore_(_,_)", 13); - } -} - - -// Emit the final ClassAttributes that exists at runtime -static void emitClassAttributes(Compiler* compiler, ClassInfo* classInfo) -{ - loadCoreVariable(compiler, "ClassAttributes"); - - classInfo->classAttributes - ? emitAttributes(compiler, classInfo->classAttributes) - : null(compiler, false); - - classInfo->methodAttributes - ? emitAttributeMethods(compiler, classInfo->methodAttributes) - : null(compiler, false); - - callMethod(compiler, 2, "new(_,_)", 8); -} - -// Copy the current attributes stored in the compiler into a destination map -// This also resets the counter, since the intent is to consume the attributes -static void copyAttributes(Compiler* compiler, ObjMap* into) -{ - compiler->numAttributes = 0; - - if(compiler->attributes->count == 0) return; - if(into == NULL) return; - - WrenVM* vm = compiler->parser->vm; - - // Note we copy the actual values as is since we'll take ownership - // and clear the original map - for(uint32_t attrIdx = 0; attrIdx < compiler->attributes->capacity; attrIdx++) - { - const MapEntry* attrEntry = &compiler->attributes->entries[attrIdx]; - if(IS_UNDEFINED(attrEntry->key)) continue; - wrenMapSet(vm, into, attrEntry->key, attrEntry->value); - } - - wrenMapClear(vm, compiler->attributes); -} - -// Copy the current attributes stored in the compiler into the method specific -// attributes for the current enclosingClass. -// This also resets the counter, since the intent is to consume the attributes -static void copyMethodAttributes(Compiler* compiler, bool isForeign, - bool isStatic, const char* fullSignature, int32_t length) -{ - compiler->numAttributes = 0; - - if(compiler->attributes->count == 0) return; - - WrenVM* vm = compiler->parser->vm; - - // Make a map for this method to copy into - ObjMap* methodAttr = wrenNewMap(vm); - wrenPushRoot(vm, (Obj*)methodAttr); - copyAttributes(compiler, methodAttr); - - // Include 'foreign static ' in front as needed - int32_t fullLength = length; - if(isForeign) fullLength += 8; - if(isStatic) fullLength += 7; - char fullSignatureWithPrefix[MAX_METHOD_SIGNATURE + 8 + 7]; - const char* foreignPrefix = isForeign ? "foreign " : ""; - const char* staticPrefix = isStatic ? "static " : ""; - sprintf(fullSignatureWithPrefix, "%s%s%.*s", foreignPrefix, staticPrefix, - length, fullSignature); - fullSignatureWithPrefix[fullLength] = '\0'; - - if(compiler->enclosingClass->methodAttributes == NULL) { - compiler->enclosingClass->methodAttributes = wrenNewMap(vm); - } - - // Store the method attributes in the class map - Value key = wrenNewStringLength(vm, fullSignatureWithPrefix, fullLength); - wrenMapSet(vm, compiler->enclosingClass->methodAttributes, key, OBJ_VAL(methodAttr)); - - wrenPopRoot(vm); -} -// End file "wren_compiler.c" -// Begin file "wren_primitive.c" -// Begin file "wren_primitive.h" -#ifndef wren_primitive_h -#define wren_primitive_h - - -// Binds a primitive method named [name] (in Wren) implemented using C function -// [fn] to `ObjClass` [cls]. -#define PRIMITIVE(cls, name, function) \ - do \ - { \ - int symbol = wrenSymbolTableEnsure(vm, \ - &vm->methodNames, name, strlen(name)); \ - Method method; \ - method.type = METHOD_PRIMITIVE; \ - method.as.primitive = prim_##function; \ - wrenBindMethod(vm, cls, symbol, method); \ - } while (false) - -// Binds a primitive method named [name] (in Wren) implemented using C function -// [fn] to `ObjClass` [cls], but as a FN call. -#define FUNCTION_CALL(cls, name, function) \ - do \ - { \ - int symbol = wrenSymbolTableEnsure(vm, \ - &vm->methodNames, name, strlen(name)); \ - Method method; \ - method.type = METHOD_FUNCTION_CALL; \ - method.as.primitive = prim_##function; \ - wrenBindMethod(vm, cls, symbol, method); \ - } while (false) - -// Defines a primitive method whose C function name is [name]. This abstracts -// the actual type signature of a primitive function and makes it clear which C -// functions are invoked as primitives. -#define DEF_PRIMITIVE(name) \ - static bool prim_##name(WrenVM* vm, Value* args) - -#define RETURN_VAL(value) \ - do \ - { \ - args[0] = value; \ - return true; \ - } while (false) - -#define RETURN_OBJ(obj) RETURN_VAL(OBJ_VAL(obj)) -#define RETURN_BOOL(value) RETURN_VAL(BOOL_VAL(value)) -#define RETURN_FALSE RETURN_VAL(FALSE_VAL) -#define RETURN_NULL RETURN_VAL(NULL_VAL) -#define RETURN_NUM(value) RETURN_VAL(NUM_VAL(value)) -#define RETURN_TRUE RETURN_VAL(TRUE_VAL) - -#define RETURN_ERROR(msg) \ - do \ - { \ - vm->fiber->error = wrenNewStringLength(vm, msg, sizeof(msg) - 1); \ - return false; \ - } while (false) - -#define RETURN_ERROR_FMT(...) \ - do \ - { \ - vm->fiber->error = wrenStringFormat(vm, __VA_ARGS__); \ - return false; \ - } while (false) - -// Validates that the given [arg] is a function. Returns true if it is. If not, -// reports an error and returns false. -bool validateFn(WrenVM* vm, Value arg, const char* argName); - -// Validates that the given [arg] is a Num. Returns true if it is. If not, -// reports an error and returns false. -bool validateNum(WrenVM* vm, Value arg, const char* argName); - -// Validates that [value] is an integer. Returns true if it is. If not, reports -// an error and returns false. -bool validateIntValue(WrenVM* vm, double value, const char* argName); - -// Validates that the given [arg] is an integer. Returns true if it is. If not, -// reports an error and returns false. -bool validateInt(WrenVM* vm, Value arg, const char* argName); - -// Validates that [arg] is a valid object for use as a map key. Returns true if -// it is. If not, reports an error and returns false. -bool validateKey(WrenVM* vm, Value arg); - -// Validates that the argument at [argIndex] is an integer within `[0, count)`. -// Also allows negative indices which map backwards from the end. Returns the -// valid positive index value. If invalid, reports an error and returns -// `UINT32_MAX`. -uint32_t validateIndex(WrenVM* vm, Value arg, uint32_t count, - const char* argName); - -// Validates that the given [arg] is a String. Returns true if it is. If not, -// reports an error and returns false. -bool validateString(WrenVM* vm, Value arg, const char* argName); - -// Given a [range] and the [length] of the object being operated on, determines -// the series of elements that should be chosen from the underlying object. -// Handles ranges that count backwards from the end as well as negative ranges. -// -// Returns the index from which the range should start or `UINT32_MAX` if the -// range is invalid. After calling, [length] will be updated with the number of -// elements in the resulting sequence. [step] will be direction that the range -// is going: `1` if the range is increasing from the start index or `-1` if the -// range is decreasing. -uint32_t calculateRange(WrenVM* vm, ObjRange* range, uint32_t* length, - int* step); - -#endif -// End file "wren_primitive.h" - -#include - -// Validates that [value] is an integer within `[0, count)`. Also allows -// negative indices which map backwards from the end. Returns the valid positive -// index value. If invalid, reports an error and returns `UINT32_MAX`. -static uint32_t validateIndexValue(WrenVM* vm, uint32_t count, double value, - const char* argName) -{ - if (!validateIntValue(vm, value, argName)) return UINT32_MAX; - - // Negative indices count from the end. - if (value < 0) value = count + value; - - // Check bounds. - if (value >= 0 && value < count) return (uint32_t)value; - - vm->fiber->error = wrenStringFormat(vm, "$ out of bounds.", argName); - return UINT32_MAX; -} - -bool validateFn(WrenVM* vm, Value arg, const char* argName) -{ - if (IS_CLOSURE(arg)) return true; - RETURN_ERROR_FMT("$ must be a function.", argName); -} - -bool validateNum(WrenVM* vm, Value arg, const char* argName) -{ - if (IS_NUM(arg)) return true; - RETURN_ERROR_FMT("$ must be a number.", argName); -} - -bool validateIntValue(WrenVM* vm, double value, const char* argName) -{ - if (trunc(value) == value) return true; - RETURN_ERROR_FMT("$ must be an integer.", argName); -} - -bool validateInt(WrenVM* vm, Value arg, const char* argName) -{ - // Make sure it's a number first. - if (!validateNum(vm, arg, argName)) return false; - return validateIntValue(vm, AS_NUM(arg), argName); -} - -bool validateKey(WrenVM* vm, Value arg) -{ - if (wrenMapIsValidKey(arg)) return true; - - RETURN_ERROR("Key must be a value type."); -} - -uint32_t validateIndex(WrenVM* vm, Value arg, uint32_t count, - const char* argName) -{ - if (!validateNum(vm, arg, argName)) return UINT32_MAX; - return validateIndexValue(vm, count, AS_NUM(arg), argName); -} - -bool validateString(WrenVM* vm, Value arg, const char* argName) -{ - if (IS_STRING(arg)) return true; - RETURN_ERROR_FMT("$ must be a string.", argName); -} - -uint32_t calculateRange(WrenVM* vm, ObjRange* range, uint32_t* length, - int* step) -{ - *step = 0; - - // Edge case: an empty range is allowed at the end of a sequence. This way, - // list[0..-1] and list[0...list.count] can be used to copy a list even when - // empty. - if (range->from == *length && - range->to == (range->isInclusive ? -1.0 : (double)*length)) - { - *length = 0; - return 0; - } - - uint32_t from = validateIndexValue(vm, *length, range->from, "Range start"); - if (from == UINT32_MAX) return UINT32_MAX; - - // Bounds check the end manually to handle exclusive ranges. - double value = range->to; - if (!validateIntValue(vm, value, "Range end")) return UINT32_MAX; - - // Negative indices count from the end. - if (value < 0) value = *length + value; - - // Convert the exclusive range to an inclusive one. - if (!range->isInclusive) - { - // An exclusive range with the same start and end points is empty. - if (value == from) - { - *length = 0; - return from; - } - - // Shift the endpoint to make it inclusive, handling both increasing and - // decreasing ranges. - value += value >= from ? -1 : 1; - } - - // Check bounds. - if (value < 0 || value >= *length) - { - vm->fiber->error = CONST_STRING(vm, "Range end out of bounds."); - return UINT32_MAX; - } - - uint32_t to = (uint32_t)value; - *length = abs((int)(from - to)) + 1; - *step = from < to ? 1 : -1; - return from; -} -// End file "wren_primitive.c" -// Begin file "wren_core.c" -#include -#include -#include -#include -#include -#include - -// Begin file "wren_core.h" -#ifndef wren_core_h -#define wren_core_h - - -// This module defines the built-in classes and their primitives methods that -// are implemented directly in C code. Some languages try to implement as much -// of the core module itself in the primary language instead of in the host -// language. -// -// With Wren, we try to do as much of it in C as possible. Primitive methods -// are always faster than code written in Wren, and it minimizes startup time -// since we don't have to parse, compile, and execute Wren code. -// -// There is one limitation, though. Methods written in C cannot call Wren ones. -// They can only be the top of the callstack, and immediately return. This -// makes it difficult to have primitive methods that rely on polymorphic -// behavior. For example, `System.print` should call `toString` on its argument, -// including user-defined `toString` methods on user-defined classes. - -void wrenInitializeCore(WrenVM* vm); - -#endif -// End file "wren_core.h" - -// Begin file "wren_core.wren.inc" -// Generated automatically from src/vm/wren_core.wren. Do not edit. -static const char* coreModuleSource = -"class Bool {}\n" -"class Fiber {}\n" -"class Fn {}\n" -"class Null {}\n" -"class Num {}\n" -"\n" -"class Sequence {\n" -" all(f) {\n" -" var result = true\n" -" for (element in this) {\n" -" result = f.call(element)\n" -" if (!result) return result\n" -" }\n" -" return result\n" -" }\n" -"\n" -" any(f) {\n" -" var result = false\n" -" for (element in this) {\n" -" result = f.call(element)\n" -" if (result) return result\n" -" }\n" -" return result\n" -" }\n" -"\n" -" contains(element) {\n" -" for (item in this) {\n" -" if (element == item) return true\n" -" }\n" -" return false\n" -" }\n" -"\n" -" count {\n" -" var result = 0\n" -" for (element in this) {\n" -" result = result + 1\n" -" }\n" -" return result\n" -" }\n" -"\n" -" count(f) {\n" -" var result = 0\n" -" for (element in this) {\n" -" if (f.call(element)) result = result + 1\n" -" }\n" -" return result\n" -" }\n" -"\n" -" each(f) {\n" -" for (element in this) {\n" -" f.call(element)\n" -" }\n" -" }\n" -"\n" -" isEmpty { iterate(null) ? false : true }\n" -"\n" -" map(transformation) { MapSequence.new(this, transformation) }\n" -"\n" -" skip(count) {\n" -" if (!(count is Num) || !count.isInteger || count < 0) {\n" -" Fiber.abort(\"Count must be a non-negative integer.\")\n" -" }\n" -"\n" -" return SkipSequence.new(this, count)\n" -" }\n" -"\n" -" take(count) {\n" -" if (!(count is Num) || !count.isInteger || count < 0) {\n" -" Fiber.abort(\"Count must be a non-negative integer.\")\n" -" }\n" -"\n" -" return TakeSequence.new(this, count)\n" -" }\n" -"\n" -" where(predicate) { WhereSequence.new(this, predicate) }\n" -"\n" -" reduce(acc, f) {\n" -" for (element in this) {\n" -" acc = f.call(acc, element)\n" -" }\n" -" return acc\n" -" }\n" -"\n" -" reduce(f) {\n" -" var iter = iterate(null)\n" -" if (!iter) Fiber.abort(\"Can't reduce an empty sequence.\")\n" -"\n" -" // Seed with the first element.\n" -" var result = iteratorValue(iter)\n" -" while (iter = iterate(iter)) {\n" -" result = f.call(result, iteratorValue(iter))\n" -" }\n" -"\n" -" return result\n" -" }\n" -"\n" -" join() { join(\"\") }\n" -"\n" -" join(sep) {\n" -" var first = true\n" -" var result = \"\"\n" -"\n" -" for (element in this) {\n" -" if (!first) result = result + sep\n" -" first = false\n" -" result = result + element.toString\n" -" }\n" -"\n" -" return result\n" -" }\n" -"\n" -" toList {\n" -" var result = List.new()\n" -" for (element in this) {\n" -" result.add(element)\n" -" }\n" -" return result\n" -" }\n" -"}\n" -"\n" -"class MapSequence is Sequence {\n" -" construct new(sequence, fn) {\n" -" _sequence = sequence\n" -" _fn = fn\n" -" }\n" -"\n" -" iterate(iterator) { _sequence.iterate(iterator) }\n" -" iteratorValue(iterator) { _fn.call(_sequence.iteratorValue(iterator)) }\n" -"}\n" -"\n" -"class SkipSequence is Sequence {\n" -" construct new(sequence, count) {\n" -" _sequence = sequence\n" -" _count = count\n" -" }\n" -"\n" -" iterate(iterator) {\n" -" if (iterator) {\n" -" return _sequence.iterate(iterator)\n" -" } else {\n" -" iterator = _sequence.iterate(iterator)\n" -" var count = _count\n" -" while (count > 0 && iterator) {\n" -" iterator = _sequence.iterate(iterator)\n" -" count = count - 1\n" -" }\n" -" return iterator\n" -" }\n" -" }\n" -"\n" -" iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n" -"}\n" -"\n" -"class TakeSequence is Sequence {\n" -" construct new(sequence, count) {\n" -" _sequence = sequence\n" -" _count = count\n" -" }\n" -"\n" -" iterate(iterator) {\n" -" if (!iterator) _taken = 1 else _taken = _taken + 1\n" -" return _taken > _count ? null : _sequence.iterate(iterator)\n" -" }\n" -"\n" -" iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n" -"}\n" -"\n" -"class WhereSequence is Sequence {\n" -" construct new(sequence, fn) {\n" -" _sequence = sequence\n" -" _fn = fn\n" -" }\n" -"\n" -" iterate(iterator) {\n" -" while (iterator = _sequence.iterate(iterator)) {\n" -" if (_fn.call(_sequence.iteratorValue(iterator))) break\n" -" }\n" -" return iterator\n" -" }\n" -"\n" -" iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n" -"}\n" -"\n" -"class String is Sequence {\n" -" bytes { StringByteSequence.new(this) }\n" -" codePoints { StringCodePointSequence.new(this) }\n" -"\n" -" split(delimiter) {\n" -" if (!(delimiter is String) || delimiter.isEmpty) {\n" -" Fiber.abort(\"Delimiter must be a non-empty string.\")\n" -" }\n" -"\n" -" var result = []\n" -"\n" -" var last = 0\n" -" var index = 0\n" -"\n" -" var delimSize = delimiter.byteCount_\n" -" var size = byteCount_\n" -"\n" -" while (last < size && (index = indexOf(delimiter, last)) != -1) {\n" -" result.add(this[last...index])\n" -" last = index + delimSize\n" -" }\n" -"\n" -" if (last < size) {\n" -" result.add(this[last..-1])\n" -" } else {\n" -" result.add(\"\")\n" -" }\n" -" return result\n" -" }\n" -"\n" -" replace(from, to) {\n" -" if (!(from is String) || from.isEmpty) {\n" -" Fiber.abort(\"From must be a non-empty string.\")\n" -" } else if (!(to is String)) {\n" -" Fiber.abort(\"To must be a string.\")\n" -" }\n" -"\n" -" var result = \"\"\n" -"\n" -" var last = 0\n" -" var index = 0\n" -"\n" -" var fromSize = from.byteCount_\n" -" var size = byteCount_\n" -"\n" -" while (last < size && (index = indexOf(from, last)) != -1) {\n" -" result = result + this[last...index] + to\n" -" last = index + fromSize\n" -" }\n" -"\n" -" if (last < size) result = result + this[last..-1]\n" -"\n" -" return result\n" -" }\n" -"\n" -" trim() { trim_(\"\\t\\r\\n \", true, true) }\n" -" trim(chars) { trim_(chars, true, true) }\n" -" trimEnd() { trim_(\"\\t\\r\\n \", false, true) }\n" -" trimEnd(chars) { trim_(chars, false, true) }\n" -" trimStart() { trim_(\"\\t\\r\\n \", true, false) }\n" -" trimStart(chars) { trim_(chars, true, false) }\n" -"\n" -" trim_(chars, trimStart, trimEnd) {\n" -" if (!(chars is String)) {\n" -" Fiber.abort(\"Characters must be a string.\")\n" -" }\n" -"\n" -" var codePoints = chars.codePoints.toList\n" -"\n" -" var start\n" -" if (trimStart) {\n" -" while (start = iterate(start)) {\n" -" if (!codePoints.contains(codePointAt_(start))) break\n" -" }\n" -"\n" -" if (start == false) return \"\"\n" -" } else {\n" -" start = 0\n" -" }\n" -"\n" -" var end\n" -" if (trimEnd) {\n" -" end = byteCount_ - 1\n" -" while (end >= start) {\n" -" var codePoint = codePointAt_(end)\n" -" if (codePoint != -1 && !codePoints.contains(codePoint)) break\n" -" end = end - 1\n" -" }\n" -"\n" -" if (end < start) return \"\"\n" -" } else {\n" -" end = -1\n" -" }\n" -"\n" -" return this[start..end]\n" -" }\n" -"\n" -" *(count) {\n" -" if (!(count is Num) || !count.isInteger || count < 0) {\n" -" Fiber.abort(\"Count must be a non-negative integer.\")\n" -" }\n" -"\n" -" var result = \"\"\n" -" for (i in 0...count) {\n" -" result = result + this\n" -" }\n" -" return result\n" -" }\n" -"}\n" -"\n" -"class StringByteSequence is Sequence {\n" -" construct new(string) {\n" -" _string = string\n" -" }\n" -"\n" -" [index] { _string.byteAt_(index) }\n" -" iterate(iterator) { _string.iterateByte_(iterator) }\n" -" iteratorValue(iterator) { _string.byteAt_(iterator) }\n" -"\n" -" count { _string.byteCount_ }\n" -"}\n" -"\n" -"class StringCodePointSequence is Sequence {\n" -" construct new(string) {\n" -" _string = string\n" -" }\n" -"\n" -" [index] { _string.codePointAt_(index) }\n" -" iterate(iterator) { _string.iterate(iterator) }\n" -" iteratorValue(iterator) { _string.codePointAt_(iterator) }\n" -"\n" -" count { _string.count }\n" -"}\n" -"\n" -"class List is Sequence {\n" -" addAll(other) {\n" -" for (element in other) {\n" -" add(element)\n" -" }\n" -" return other\n" -" }\n" -"\n" -" sort() { sort {|low, high| low < high } }\n" -"\n" -" sort(comparer) {\n" -" if (!(comparer is Fn)) {\n" -" Fiber.abort(\"Comparer must be a function.\")\n" -" }\n" -" quicksort_(0, count - 1, comparer)\n" -" return this\n" -" }\n" -"\n" -" quicksort_(low, high, comparer) {\n" -" if (low < high) {\n" -" var p = partition_(low, high, comparer)\n" -" quicksort_(low, p - 1, comparer)\n" -" quicksort_(p + 1, high, comparer)\n" -" }\n" -" }\n" -"\n" -" partition_(low, high, comparer) {\n" -" var p = this[high]\n" -" var i = low - 1\n" -" for (j in low..(high-1)) {\n" -" if (comparer.call(this[j], p)) { \n" -" i = i + 1\n" -" var t = this[i]\n" -" this[i] = this[j]\n" -" this[j] = t\n" -" }\n" -" }\n" -" var t = this[i+1]\n" -" this[i+1] = this[high]\n" -" this[high] = t\n" -" return i+1\n" -" }\n" -"\n" -" toString { \"[%(join(\", \"))]\" }\n" -"\n" -" +(other) {\n" -" var result = this[0..-1]\n" -" for (element in other) {\n" -" result.add(element)\n" -" }\n" -" return result\n" -" }\n" -"\n" -" *(count) {\n" -" if (!(count is Num) || !count.isInteger || count < 0) {\n" -" Fiber.abort(\"Count must be a non-negative integer.\")\n" -" }\n" -"\n" -" var result = []\n" -" for (i in 0...count) {\n" -" result.addAll(this)\n" -" }\n" -" return result\n" -" }\n" -"}\n" -"\n" -"class Map is Sequence {\n" -" keys { MapKeySequence.new(this) }\n" -" values { MapValueSequence.new(this) }\n" -"\n" -" toString {\n" -" var first = true\n" -" var result = \"{\"\n" -"\n" -" for (key in keys) {\n" -" if (!first) result = result + \", \"\n" -" first = false\n" -" result = result + \"%(key): %(this[key])\"\n" -" }\n" -"\n" -" return result + \"}\"\n" -" }\n" -"\n" -" iteratorValue(iterator) {\n" -" return MapEntry.new(\n" -" keyIteratorValue_(iterator),\n" -" valueIteratorValue_(iterator))\n" -" }\n" -"}\n" -"\n" -"class MapEntry {\n" -" construct new(key, value) {\n" -" _key = key\n" -" _value = value\n" -" }\n" -"\n" -" key { _key }\n" -" value { _value }\n" -"\n" -" toString { \"%(_key):%(_value)\" }\n" -"}\n" -"\n" -"class MapKeySequence is Sequence {\n" -" construct new(map) {\n" -" _map = map\n" -" }\n" -"\n" -" iterate(n) { _map.iterate(n) }\n" -" iteratorValue(iterator) { _map.keyIteratorValue_(iterator) }\n" -"}\n" -"\n" -"class MapValueSequence is Sequence {\n" -" construct new(map) {\n" -" _map = map\n" -" }\n" -"\n" -" iterate(n) { _map.iterate(n) }\n" -" iteratorValue(iterator) { _map.valueIteratorValue_(iterator) }\n" -"}\n" -"\n" -"class Range is Sequence {}\n" -"\n" -"class System {\n" -" static print() {\n" -" writeString_(\"\\n\")\n" -" }\n" -"\n" -" static print(obj) {\n" -" writeObject_(obj)\n" -" writeString_(\"\\n\")\n" -" return obj\n" -" }\n" -"\n" -" static printAll(sequence) {\n" -" for (object in sequence) writeObject_(object)\n" -" writeString_(\"\\n\")\n" -" }\n" -"\n" -" static write(obj) {\n" -" writeObject_(obj)\n" -" return obj\n" -" }\n" -"\n" -" static writeAll(sequence) {\n" -" for (object in sequence) writeObject_(object)\n" -" }\n" -"\n" -" static writeObject_(obj) {\n" -" var string = obj.toString\n" -" if (string is String) {\n" -" writeString_(string)\n" -" } else {\n" -" writeString_(\"[invalid toString]\")\n" -" }\n" -" }\n" -"}\n" -"\n" -"class ClassAttributes {\n" -" self { _attributes }\n" -" methods { _methods }\n" -" construct new(attributes, methods) {\n" -" _attributes = attributes\n" -" _methods = methods\n" -" }\n" -" toString { \"attributes:%(_attributes) methods:%(_methods)\" }\n" -"}\n"; -// End file "wren_core.wren.inc" - -DEF_PRIMITIVE(bool_not) -{ - RETURN_BOOL(!AS_BOOL(args[0])); -} - -DEF_PRIMITIVE(bool_toString) -{ - if (AS_BOOL(args[0])) - { - RETURN_VAL(CONST_STRING(vm, "true")); - } - else - { - RETURN_VAL(CONST_STRING(vm, "false")); - } -} - -DEF_PRIMITIVE(class_name) -{ - RETURN_OBJ(AS_CLASS(args[0])->name); -} - -DEF_PRIMITIVE(class_supertype) -{ - ObjClass* classObj = AS_CLASS(args[0]); - - // Object has no superclass. - if (classObj->superclass == NULL) RETURN_NULL; - - RETURN_OBJ(classObj->superclass); -} - -DEF_PRIMITIVE(class_toString) -{ - RETURN_OBJ(AS_CLASS(args[0])->name); -} - -DEF_PRIMITIVE(class_attributes) -{ - RETURN_VAL(AS_CLASS(args[0])->attributes); -} - -DEF_PRIMITIVE(fiber_new) -{ - if (!validateFn(vm, args[1], "Argument")) return false; - - ObjClosure* closure = AS_CLOSURE(args[1]); - if (closure->fn->arity > 1) - { - RETURN_ERROR("Function cannot take more than one parameter."); - } - - RETURN_OBJ(wrenNewFiber(vm, closure)); -} - -DEF_PRIMITIVE(fiber_abort) -{ - vm->fiber->error = args[1]; - - // If the error is explicitly null, it's not really an abort. - return IS_NULL(args[1]); -} - -// Transfer execution to [fiber] coming from the current fiber whose stack has -// [args]. -// -// [isCall] is true if [fiber] is being called and not transferred. -// -// [hasValue] is true if a value in [args] is being passed to the new fiber. -// Otherwise, `null` is implicitly being passed. -static bool runFiber(WrenVM* vm, ObjFiber* fiber, Value* args, bool isCall, - bool hasValue, const char* verb) -{ - - if (wrenHasError(fiber)) - { - RETURN_ERROR_FMT("Cannot $ an aborted fiber.", verb); - } - - if (isCall) - { - // You can't call a called fiber, but you can transfer directly to it, - // which is why this check is gated on `isCall`. This way, after resuming a - // suspended fiber, it will run and then return to the fiber that called it - // and so on. - if (fiber->caller != NULL) RETURN_ERROR("Fiber has already been called."); - - if (fiber->state == FIBER_ROOT) RETURN_ERROR("Cannot call root fiber."); - - // Remember who ran it. - fiber->caller = vm->fiber; - } - - if (fiber->numFrames == 0) - { - RETURN_ERROR_FMT("Cannot $ a finished fiber.", verb); - } - - // When the calling fiber resumes, we'll store the result of the call in its - // stack. If the call has two arguments (the fiber and the value), we only - // need one slot for the result, so discard the other slot now. - if (hasValue) vm->fiber->stackTop--; - - if (fiber->numFrames == 1 && - fiber->frames[0].ip == fiber->frames[0].closure->fn->code.data) - { - // The fiber is being started for the first time. If its function takes a - // parameter, bind an argument to it. - if (fiber->frames[0].closure->fn->arity == 1) - { - fiber->stackTop[0] = hasValue ? args[1] : NULL_VAL; - fiber->stackTop++; - } - } - else - { - // The fiber is being resumed, make yield() or transfer() return the result. - fiber->stackTop[-1] = hasValue ? args[1] : NULL_VAL; - } - - vm->fiber = fiber; - return false; -} - -DEF_PRIMITIVE(fiber_call) -{ - return runFiber(vm, AS_FIBER(args[0]), args, true, false, "call"); -} - -DEF_PRIMITIVE(fiber_call1) -{ - return runFiber(vm, AS_FIBER(args[0]), args, true, true, "call"); -} - -DEF_PRIMITIVE(fiber_current) -{ - RETURN_OBJ(vm->fiber); -} - -DEF_PRIMITIVE(fiber_error) -{ - RETURN_VAL(AS_FIBER(args[0])->error); -} - -DEF_PRIMITIVE(fiber_isDone) -{ - ObjFiber* runFiber = AS_FIBER(args[0]); - RETURN_BOOL(runFiber->numFrames == 0 || wrenHasError(runFiber)); -} - -DEF_PRIMITIVE(fiber_suspend) -{ - // Switching to a null fiber tells the interpreter to stop and exit. - vm->fiber = NULL; - vm->apiStack = NULL; - return false; -} - -DEF_PRIMITIVE(fiber_transfer) -{ - return runFiber(vm, AS_FIBER(args[0]), args, false, false, "transfer to"); -} - -DEF_PRIMITIVE(fiber_transfer1) -{ - return runFiber(vm, AS_FIBER(args[0]), args, false, true, "transfer to"); -} - -DEF_PRIMITIVE(fiber_transferError) -{ - runFiber(vm, AS_FIBER(args[0]), args, false, true, "transfer to"); - vm->fiber->error = args[1]; - return false; -} - -DEF_PRIMITIVE(fiber_try) -{ - runFiber(vm, AS_FIBER(args[0]), args, true, false, "try"); - - // If we're switching to a valid fiber to try, remember that we're trying it. - if (!wrenHasError(vm->fiber)) vm->fiber->state = FIBER_TRY; - return false; -} - -DEF_PRIMITIVE(fiber_try1) -{ - runFiber(vm, AS_FIBER(args[0]), args, true, true, "try"); - - // If we're switching to a valid fiber to try, remember that we're trying it. - if (!wrenHasError(vm->fiber)) vm->fiber->state = FIBER_TRY; - return false; -} - -DEF_PRIMITIVE(fiber_yield) -{ - ObjFiber* current = vm->fiber; - vm->fiber = current->caller; - - // Unhook this fiber from the one that called it. - current->caller = NULL; - current->state = FIBER_OTHER; - - if (vm->fiber != NULL) - { - // Make the caller's run method return null. - vm->fiber->stackTop[-1] = NULL_VAL; - } - - return false; -} - -DEF_PRIMITIVE(fiber_yield1) -{ - ObjFiber* current = vm->fiber; - vm->fiber = current->caller; - - // Unhook this fiber from the one that called it. - current->caller = NULL; - current->state = FIBER_OTHER; - - if (vm->fiber != NULL) - { - // Make the caller's run method return the argument passed to yield. - vm->fiber->stackTop[-1] = args[1]; - - // When the yielding fiber resumes, we'll store the result of the yield - // call in its stack. Since Fiber.yield(value) has two arguments (the Fiber - // class and the value) and we only need one slot for the result, discard - // the other slot now. - current->stackTop--; - } - - return false; -} - -DEF_PRIMITIVE(fn_new) -{ - if (!validateFn(vm, args[1], "Argument")) return false; - - // The block argument is already a function, so just return it. - RETURN_VAL(args[1]); -} - -DEF_PRIMITIVE(fn_arity) -{ - RETURN_NUM(AS_CLOSURE(args[0])->fn->arity); -} - -static void call_fn(WrenVM* vm, Value* args, int numArgs) -{ - // +1 to include the function itself. - wrenCallFunction(vm, vm->fiber, AS_CLOSURE(args[0]), numArgs + 1); -} - -#define DEF_FN_CALL(numArgs) \ - DEF_PRIMITIVE(fn_call##numArgs) \ - { \ - call_fn(vm, args, numArgs); \ - return false; \ - } - -DEF_FN_CALL(0) -DEF_FN_CALL(1) -DEF_FN_CALL(2) -DEF_FN_CALL(3) -DEF_FN_CALL(4) -DEF_FN_CALL(5) -DEF_FN_CALL(6) -DEF_FN_CALL(7) -DEF_FN_CALL(8) -DEF_FN_CALL(9) -DEF_FN_CALL(10) -DEF_FN_CALL(11) -DEF_FN_CALL(12) -DEF_FN_CALL(13) -DEF_FN_CALL(14) -DEF_FN_CALL(15) -DEF_FN_CALL(16) - -DEF_PRIMITIVE(fn_toString) -{ - RETURN_VAL(CONST_STRING(vm, "")); -} - -// Creates a new list of size args[1], with all elements initialized to args[2]. -DEF_PRIMITIVE(list_filled) -{ - if (!validateInt(vm, args[1], "Size")) return false; - if (AS_NUM(args[1]) < 0) RETURN_ERROR("Size cannot be negative."); - - uint32_t size = (uint32_t)AS_NUM(args[1]); - ObjList* list = wrenNewList(vm, size); - - for (uint32_t i = 0; i < size; i++) - { - list->elements.data[i] = args[2]; - } - - RETURN_OBJ(list); -} - -DEF_PRIMITIVE(list_new) -{ - RETURN_OBJ(wrenNewList(vm, 0)); -} - -DEF_PRIMITIVE(list_add) -{ - wrenValueBufferWrite(vm, &AS_LIST(args[0])->elements, args[1]); - RETURN_VAL(args[1]); -} - -// Adds an element to the list and then returns the list itself. This is called -// by the compiler when compiling list literals instead of using add() to -// minimize stack churn. -DEF_PRIMITIVE(list_addCore) -{ - wrenValueBufferWrite(vm, &AS_LIST(args[0])->elements, args[1]); - - // Return the list. - RETURN_VAL(args[0]); -} - -DEF_PRIMITIVE(list_clear) -{ - wrenValueBufferClear(vm, &AS_LIST(args[0])->elements); - RETURN_NULL; -} - -DEF_PRIMITIVE(list_count) -{ - RETURN_NUM(AS_LIST(args[0])->elements.count); -} - -DEF_PRIMITIVE(list_insert) -{ - ObjList* list = AS_LIST(args[0]); - - // count + 1 here so you can "insert" at the very end. - uint32_t index = validateIndex(vm, args[1], list->elements.count + 1, - "Index"); - if (index == UINT32_MAX) return false; - - wrenListInsert(vm, list, args[2], index); - RETURN_VAL(args[2]); -} - -DEF_PRIMITIVE(list_iterate) -{ - ObjList* list = AS_LIST(args[0]); - - // If we're starting the iteration, return the first index. - if (IS_NULL(args[1])) - { - if (list->elements.count == 0) RETURN_FALSE; - RETURN_NUM(0); - } - - if (!validateInt(vm, args[1], "Iterator")) return false; - - // Stop if we're out of bounds. - double index = AS_NUM(args[1]); - if (index < 0 || index >= list->elements.count - 1) RETURN_FALSE; - - // Otherwise, move to the next index. - RETURN_NUM(index + 1); -} - -DEF_PRIMITIVE(list_iteratorValue) -{ - ObjList* list = AS_LIST(args[0]); - uint32_t index = validateIndex(vm, args[1], list->elements.count, "Iterator"); - if (index == UINT32_MAX) return false; - - RETURN_VAL(list->elements.data[index]); -} - -DEF_PRIMITIVE(list_removeAt) -{ - ObjList* list = AS_LIST(args[0]); - uint32_t index = validateIndex(vm, args[1], list->elements.count, "Index"); - if (index == UINT32_MAX) return false; - - RETURN_VAL(wrenListRemoveAt(vm, list, index)); -} - -DEF_PRIMITIVE(list_removeValue) { - ObjList* list = AS_LIST(args[0]); - int index = wrenListIndexOf(vm, list, args[1]); - if(index == -1) RETURN_NULL; - RETURN_VAL(wrenListRemoveAt(vm, list, index)); -} - -DEF_PRIMITIVE(list_indexOf) -{ - ObjList* list = AS_LIST(args[0]); - RETURN_NUM(wrenListIndexOf(vm, list, args[1])); -} - -DEF_PRIMITIVE(list_swap) -{ - ObjList* list = AS_LIST(args[0]); - uint32_t indexA = validateIndex(vm, args[1], list->elements.count, "Index 0"); - if (indexA == UINT32_MAX) return false; - uint32_t indexB = validateIndex(vm, args[2], list->elements.count, "Index 1"); - if (indexB == UINT32_MAX) return false; - - Value a = list->elements.data[indexA]; - list->elements.data[indexA] = list->elements.data[indexB]; - list->elements.data[indexB] = a; - - RETURN_NULL; -} - -DEF_PRIMITIVE(list_subscript) -{ - ObjList* list = AS_LIST(args[0]); - - if (IS_NUM(args[1])) - { - uint32_t index = validateIndex(vm, args[1], list->elements.count, - "Subscript"); - if (index == UINT32_MAX) return false; - - RETURN_VAL(list->elements.data[index]); - } - - if (!IS_RANGE(args[1])) - { - RETURN_ERROR("Subscript must be a number or a range."); - } - - int step; - uint32_t count = list->elements.count; - uint32_t start = calculateRange(vm, AS_RANGE(args[1]), &count, &step); - if (start == UINT32_MAX) return false; - - ObjList* result = wrenNewList(vm, count); - for (uint32_t i = 0; i < count; i++) - { - result->elements.data[i] = list->elements.data[start + i * step]; - } - - RETURN_OBJ(result); -} - -DEF_PRIMITIVE(list_subscriptSetter) -{ - ObjList* list = AS_LIST(args[0]); - uint32_t index = validateIndex(vm, args[1], list->elements.count, - "Subscript"); - if (index == UINT32_MAX) return false; - - list->elements.data[index] = args[2]; - RETURN_VAL(args[2]); -} - -DEF_PRIMITIVE(map_new) -{ - RETURN_OBJ(wrenNewMap(vm)); -} - -DEF_PRIMITIVE(map_subscript) -{ - if (!validateKey(vm, args[1])) return false; - - ObjMap* map = AS_MAP(args[0]); - Value value = wrenMapGet(map, args[1]); - if (IS_UNDEFINED(value)) RETURN_NULL; - - RETURN_VAL(value); -} - -DEF_PRIMITIVE(map_subscriptSetter) -{ - if (!validateKey(vm, args[1])) return false; - - wrenMapSet(vm, AS_MAP(args[0]), args[1], args[2]); - RETURN_VAL(args[2]); -} - -// Adds an entry to the map and then returns the map itself. This is called by -// the compiler when compiling map literals instead of using [_]=(_) to -// minimize stack churn. -DEF_PRIMITIVE(map_addCore) -{ - if (!validateKey(vm, args[1])) return false; - - wrenMapSet(vm, AS_MAP(args[0]), args[1], args[2]); - - // Return the map itself. - RETURN_VAL(args[0]); -} - -DEF_PRIMITIVE(map_clear) -{ - wrenMapClear(vm, AS_MAP(args[0])); - RETURN_NULL; -} - -DEF_PRIMITIVE(map_containsKey) -{ - if (!validateKey(vm, args[1])) return false; - - RETURN_BOOL(!IS_UNDEFINED(wrenMapGet(AS_MAP(args[0]), args[1]))); -} - -DEF_PRIMITIVE(map_count) -{ - RETURN_NUM(AS_MAP(args[0])->count); -} - -DEF_PRIMITIVE(map_iterate) -{ - ObjMap* map = AS_MAP(args[0]); - - if (map->count == 0) RETURN_FALSE; - - // If we're starting the iteration, start at the first used entry. - uint32_t index = 0; - - // Otherwise, start one past the last entry we stopped at. - if (!IS_NULL(args[1])) - { - if (!validateInt(vm, args[1], "Iterator")) return false; - - if (AS_NUM(args[1]) < 0) RETURN_FALSE; - index = (uint32_t)AS_NUM(args[1]); - - if (index >= map->capacity) RETURN_FALSE; - - // Advance the iterator. - index++; - } - - // Find a used entry, if any. - for (; index < map->capacity; index++) - { - if (!IS_UNDEFINED(map->entries[index].key)) RETURN_NUM(index); - } - - // If we get here, walked all of the entries. - RETURN_FALSE; -} - -DEF_PRIMITIVE(map_remove) -{ - if (!validateKey(vm, args[1])) return false; - - RETURN_VAL(wrenMapRemoveKey(vm, AS_MAP(args[0]), args[1])); -} - -DEF_PRIMITIVE(map_keyIteratorValue) -{ - ObjMap* map = AS_MAP(args[0]); - uint32_t index = validateIndex(vm, args[1], map->capacity, "Iterator"); - if (index == UINT32_MAX) return false; - - MapEntry* entry = &map->entries[index]; - if (IS_UNDEFINED(entry->key)) - { - RETURN_ERROR("Invalid map iterator."); - } - - RETURN_VAL(entry->key); -} - -DEF_PRIMITIVE(map_valueIteratorValue) -{ - ObjMap* map = AS_MAP(args[0]); - uint32_t index = validateIndex(vm, args[1], map->capacity, "Iterator"); - if (index == UINT32_MAX) return false; - - MapEntry* entry = &map->entries[index]; - if (IS_UNDEFINED(entry->key)) - { - RETURN_ERROR("Invalid map iterator."); - } - - RETURN_VAL(entry->value); -} - -DEF_PRIMITIVE(null_not) -{ - RETURN_VAL(TRUE_VAL); -} - -DEF_PRIMITIVE(null_toString) -{ - RETURN_VAL(CONST_STRING(vm, "null")); -} - -DEF_PRIMITIVE(num_fromString) -{ - if (!validateString(vm, args[1], "Argument")) return false; - - ObjString* string = AS_STRING(args[1]); - - // Corner case: Can't parse an empty string. - if (string->length == 0) RETURN_NULL; - - errno = 0; - char* end; - double number = strtod(string->value, &end); - - // Skip past any trailing whitespace. - while (*end != '\0' && isspace((unsigned char)*end)) end++; - - if (errno == ERANGE) RETURN_ERROR("Number literal is too large."); - - // We must have consumed the entire string. Otherwise, it contains non-number - // characters and we can't parse it. - if (end < string->value + string->length) RETURN_NULL; - - RETURN_NUM(number); -} - -// Defines a primitive on Num that calls infix [op] and returns [type]. -#define DEF_NUM_CONSTANT(name, value) \ - DEF_PRIMITIVE(num_##name) \ - { \ - RETURN_NUM(value); \ - } - -DEF_NUM_CONSTANT(infinity, INFINITY) -DEF_NUM_CONSTANT(nan, WREN_DOUBLE_NAN) -DEF_NUM_CONSTANT(pi, 3.14159265358979323846264338327950288) -DEF_NUM_CONSTANT(tau, 6.28318530717958647692528676655900577) - -DEF_NUM_CONSTANT(largest, DBL_MAX) -DEF_NUM_CONSTANT(smallest, DBL_MIN) - -DEF_NUM_CONSTANT(maxSafeInteger, 9007199254740991.0) -DEF_NUM_CONSTANT(minSafeInteger, -9007199254740991.0) - -// Defines a primitive on Num that calls infix [op] and returns [type]. -#define DEF_NUM_INFIX(name, op, type) \ - DEF_PRIMITIVE(num_##name) \ - { \ - if (!validateNum(vm, args[1], "Right operand")) return false; \ - RETURN_##type(AS_NUM(args[0]) op AS_NUM(args[1])); \ - } - -DEF_NUM_INFIX(minus, -, NUM) -DEF_NUM_INFIX(plus, +, NUM) -DEF_NUM_INFIX(multiply, *, NUM) -DEF_NUM_INFIX(divide, /, NUM) -DEF_NUM_INFIX(lt, <, BOOL) -DEF_NUM_INFIX(gt, >, BOOL) -DEF_NUM_INFIX(lte, <=, BOOL) -DEF_NUM_INFIX(gte, >=, BOOL) - -// Defines a primitive on Num that call infix bitwise [op]. -#define DEF_NUM_BITWISE(name, op) \ - DEF_PRIMITIVE(num_bitwise##name) \ - { \ - if (!validateNum(vm, args[1], "Right operand")) return false; \ - uint32_t left = (uint32_t)AS_NUM(args[0]); \ - uint32_t right = (uint32_t)AS_NUM(args[1]); \ - RETURN_NUM(left op right); \ - } - -DEF_NUM_BITWISE(And, &) -DEF_NUM_BITWISE(Or, |) -DEF_NUM_BITWISE(Xor, ^) -DEF_NUM_BITWISE(LeftShift, <<) -DEF_NUM_BITWISE(RightShift, >>) - -// Defines a primitive method on Num that returns the result of [fn]. -#define DEF_NUM_FN(name, fn) \ - DEF_PRIMITIVE(num_##name) \ - { \ - RETURN_NUM(fn(AS_NUM(args[0]))); \ - } - -DEF_NUM_FN(abs, fabs) -DEF_NUM_FN(acos, acos) -DEF_NUM_FN(asin, asin) -DEF_NUM_FN(atan, atan) -DEF_NUM_FN(cbrt, cbrt) -DEF_NUM_FN(ceil, ceil) -DEF_NUM_FN(cos, cos) -DEF_NUM_FN(floor, floor) -DEF_NUM_FN(negate, -) -DEF_NUM_FN(round, round) -DEF_NUM_FN(sin, sin) -DEF_NUM_FN(sqrt, sqrt) -DEF_NUM_FN(tan, tan) -DEF_NUM_FN(log, log) -DEF_NUM_FN(log2, log2) -DEF_NUM_FN(exp, exp) - -DEF_PRIMITIVE(num_mod) -{ - if (!validateNum(vm, args[1], "Right operand")) return false; - RETURN_NUM(fmod(AS_NUM(args[0]), AS_NUM(args[1]))); -} - -DEF_PRIMITIVE(num_eqeq) -{ - if (!IS_NUM(args[1])) RETURN_FALSE; - RETURN_BOOL(AS_NUM(args[0]) == AS_NUM(args[1])); -} - -DEF_PRIMITIVE(num_bangeq) -{ - if (!IS_NUM(args[1])) RETURN_TRUE; - RETURN_BOOL(AS_NUM(args[0]) != AS_NUM(args[1])); -} - -DEF_PRIMITIVE(num_bitwiseNot) -{ - // Bitwise operators always work on 32-bit unsigned ints. - RETURN_NUM(~(uint32_t)AS_NUM(args[0])); -} - -DEF_PRIMITIVE(num_dotDot) -{ - if (!validateNum(vm, args[1], "Right hand side of range")) return false; - - double from = AS_NUM(args[0]); - double to = AS_NUM(args[1]); - RETURN_VAL(wrenNewRange(vm, from, to, true)); -} - -DEF_PRIMITIVE(num_dotDotDot) -{ - if (!validateNum(vm, args[1], "Right hand side of range")) return false; - - double from = AS_NUM(args[0]); - double to = AS_NUM(args[1]); - RETURN_VAL(wrenNewRange(vm, from, to, false)); -} - -DEF_PRIMITIVE(num_atan2) -{ - if (!validateNum(vm, args[1], "x value")) return false; - - RETURN_NUM(atan2(AS_NUM(args[0]), AS_NUM(args[1]))); -} - -DEF_PRIMITIVE(num_min) -{ - if (!validateNum(vm, args[1], "Other value")) return false; - - double value = AS_NUM(args[0]); - double other = AS_NUM(args[1]); - RETURN_NUM(value <= other ? value : other); -} - -DEF_PRIMITIVE(num_max) -{ - if (!validateNum(vm, args[1], "Other value")) return false; - - double value = AS_NUM(args[0]); - double other = AS_NUM(args[1]); - RETURN_NUM(value > other ? value : other); -} - -DEF_PRIMITIVE(num_clamp) -{ - if (!validateNum(vm, args[1], "Min value")) return false; - if (!validateNum(vm, args[2], "Max value")) return false; - - double value = AS_NUM(args[0]); - double min = AS_NUM(args[1]); - double max = AS_NUM(args[2]); - double result = (value < min) ? min : ((value > max) ? max : value); - RETURN_NUM(result); -} - -DEF_PRIMITIVE(num_pow) -{ - if (!validateNum(vm, args[1], "Power value")) return false; - - RETURN_NUM(pow(AS_NUM(args[0]), AS_NUM(args[1]))); -} - -DEF_PRIMITIVE(num_fraction) -{ - double unused; - RETURN_NUM(modf(AS_NUM(args[0]) , &unused)); -} - -DEF_PRIMITIVE(num_isInfinity) -{ - RETURN_BOOL(isinf(AS_NUM(args[0]))); -} - -DEF_PRIMITIVE(num_isInteger) -{ - double value = AS_NUM(args[0]); - if (isnan(value) || isinf(value)) RETURN_FALSE; - RETURN_BOOL(trunc(value) == value); -} - -DEF_PRIMITIVE(num_isNan) -{ - RETURN_BOOL(isnan(AS_NUM(args[0]))); -} - -DEF_PRIMITIVE(num_sign) -{ - double value = AS_NUM(args[0]); - if (value > 0) - { - RETURN_NUM(1); - } - else if (value < 0) - { - RETURN_NUM(-1); - } - else - { - RETURN_NUM(0); - } -} - -DEF_PRIMITIVE(num_toString) -{ - RETURN_VAL(wrenNumToString(vm, AS_NUM(args[0]))); -} - -DEF_PRIMITIVE(num_truncate) -{ - double integer; - modf(AS_NUM(args[0]) , &integer); - RETURN_NUM(integer); -} - -DEF_PRIMITIVE(object_same) -{ - RETURN_BOOL(wrenValuesEqual(args[1], args[2])); -} - -DEF_PRIMITIVE(object_not) -{ - RETURN_VAL(FALSE_VAL); -} - -DEF_PRIMITIVE(object_eqeq) -{ - RETURN_BOOL(wrenValuesEqual(args[0], args[1])); -} - -DEF_PRIMITIVE(object_bangeq) -{ - RETURN_BOOL(!wrenValuesEqual(args[0], args[1])); -} - -DEF_PRIMITIVE(object_is) -{ - if (!IS_CLASS(args[1])) - { - RETURN_ERROR("Right operand must be a class."); - } - - ObjClass *classObj = wrenGetClass(vm, args[0]); - ObjClass *baseClassObj = AS_CLASS(args[1]); - - // Walk the superclass chain looking for the class. - do - { - if (baseClassObj == classObj) RETURN_BOOL(true); - - classObj = classObj->superclass; - } - while (classObj != NULL); - - RETURN_BOOL(false); -} - -DEF_PRIMITIVE(object_toString) -{ - Obj* obj = AS_OBJ(args[0]); - Value name = OBJ_VAL(obj->classObj->name); - RETURN_VAL(wrenStringFormat(vm, "instance of @", name)); -} - -DEF_PRIMITIVE(object_type) -{ - RETURN_OBJ(wrenGetClass(vm, args[0])); -} - -DEF_PRIMITIVE(range_from) -{ - RETURN_NUM(AS_RANGE(args[0])->from); -} - -DEF_PRIMITIVE(range_to) -{ - RETURN_NUM(AS_RANGE(args[0])->to); -} - -DEF_PRIMITIVE(range_min) -{ - ObjRange* range = AS_RANGE(args[0]); - RETURN_NUM(fmin(range->from, range->to)); -} - -DEF_PRIMITIVE(range_max) -{ - ObjRange* range = AS_RANGE(args[0]); - RETURN_NUM(fmax(range->from, range->to)); -} - -DEF_PRIMITIVE(range_isInclusive) -{ - RETURN_BOOL(AS_RANGE(args[0])->isInclusive); -} - -DEF_PRIMITIVE(range_iterate) -{ - ObjRange* range = AS_RANGE(args[0]); - - // Special case: empty range. - if (range->from == range->to && !range->isInclusive) RETURN_FALSE; - - // Start the iteration. - if (IS_NULL(args[1])) RETURN_NUM(range->from); - - if (!validateNum(vm, args[1], "Iterator")) return false; - - double iterator = AS_NUM(args[1]); - - // Iterate towards [to] from [from]. - if (range->from < range->to) - { - iterator++; - if (iterator > range->to) RETURN_FALSE; - } - else - { - iterator--; - if (iterator < range->to) RETURN_FALSE; - } - - if (!range->isInclusive && iterator == range->to) RETURN_FALSE; - - RETURN_NUM(iterator); -} - -DEF_PRIMITIVE(range_iteratorValue) -{ - // Assume the iterator is a number so that is the value of the range. - RETURN_VAL(args[1]); -} - -DEF_PRIMITIVE(range_toString) -{ - ObjRange* range = AS_RANGE(args[0]); - - Value from = wrenNumToString(vm, range->from); - wrenPushRoot(vm, AS_OBJ(from)); - - Value to = wrenNumToString(vm, range->to); - wrenPushRoot(vm, AS_OBJ(to)); - - Value result = wrenStringFormat(vm, "@$@", from, - range->isInclusive ? ".." : "...", to); - - wrenPopRoot(vm); - wrenPopRoot(vm); - RETURN_VAL(result); -} - -DEF_PRIMITIVE(string_fromCodePoint) -{ - if (!validateInt(vm, args[1], "Code point")) return false; - - int codePoint = (int)AS_NUM(args[1]); - if (codePoint < 0) - { - RETURN_ERROR("Code point cannot be negative."); - } - else if (codePoint > 0x10ffff) - { - RETURN_ERROR("Code point cannot be greater than 0x10ffff."); - } - - RETURN_VAL(wrenStringFromCodePoint(vm, codePoint)); -} - -DEF_PRIMITIVE(string_fromByte) -{ - if (!validateInt(vm, args[1], "Byte")) return false; - int byte = (int) AS_NUM(args[1]); - if (byte < 0) - { - RETURN_ERROR("Byte cannot be negative."); - } - else if (byte > 0xff) - { - RETURN_ERROR("Byte cannot be greater than 0xff."); - } - RETURN_VAL(wrenStringFromByte(vm, (uint8_t) byte)); -} - -DEF_PRIMITIVE(string_byteAt) -{ - ObjString* string = AS_STRING(args[0]); - - uint32_t index = validateIndex(vm, args[1], string->length, "Index"); - if (index == UINT32_MAX) return false; - - RETURN_NUM((uint8_t)string->value[index]); -} - -DEF_PRIMITIVE(string_byteCount) -{ - RETURN_NUM(AS_STRING(args[0])->length); -} - -DEF_PRIMITIVE(string_codePointAt) -{ - ObjString* string = AS_STRING(args[0]); - - uint32_t index = validateIndex(vm, args[1], string->length, "Index"); - if (index == UINT32_MAX) return false; - - // If we are in the middle of a UTF-8 sequence, indicate that. - const uint8_t* bytes = (uint8_t*)string->value; - if ((bytes[index] & 0xc0) == 0x80) RETURN_NUM(-1); - - // Decode the UTF-8 sequence. - RETURN_NUM(wrenUtf8Decode((uint8_t*)string->value + index, - string->length - index)); -} - -DEF_PRIMITIVE(string_contains) -{ - if (!validateString(vm, args[1], "Argument")) return false; - - ObjString* string = AS_STRING(args[0]); - ObjString* search = AS_STRING(args[1]); - - RETURN_BOOL(wrenStringFind(string, search, 0) != UINT32_MAX); -} - -DEF_PRIMITIVE(string_endsWith) -{ - if (!validateString(vm, args[1], "Argument")) return false; - - ObjString* string = AS_STRING(args[0]); - ObjString* search = AS_STRING(args[1]); - - // Edge case: If the search string is longer then return false right away. - if (search->length > string->length) RETURN_FALSE; - - RETURN_BOOL(memcmp(string->value + string->length - search->length, - search->value, search->length) == 0); -} - -DEF_PRIMITIVE(string_indexOf1) -{ - if (!validateString(vm, args[1], "Argument")) return false; - - ObjString* string = AS_STRING(args[0]); - ObjString* search = AS_STRING(args[1]); - - uint32_t index = wrenStringFind(string, search, 0); - RETURN_NUM(index == UINT32_MAX ? -1 : (int)index); -} - -DEF_PRIMITIVE(string_indexOf2) -{ - if (!validateString(vm, args[1], "Argument")) return false; - - ObjString* string = AS_STRING(args[0]); - ObjString* search = AS_STRING(args[1]); - uint32_t start = validateIndex(vm, args[2], string->length, "Start"); - if (start == UINT32_MAX) return false; - - uint32_t index = wrenStringFind(string, search, start); - RETURN_NUM(index == UINT32_MAX ? -1 : (int)index); -} - -DEF_PRIMITIVE(string_iterate) -{ - ObjString* string = AS_STRING(args[0]); - - // If we're starting the iteration, return the first index. - if (IS_NULL(args[1])) - { - if (string->length == 0) RETURN_FALSE; - RETURN_NUM(0); - } - - if (!validateInt(vm, args[1], "Iterator")) return false; - - if (AS_NUM(args[1]) < 0) RETURN_FALSE; - uint32_t index = (uint32_t)AS_NUM(args[1]); - - // Advance to the beginning of the next UTF-8 sequence. - do - { - index++; - if (index >= string->length) RETURN_FALSE; - } while ((string->value[index] & 0xc0) == 0x80); - - RETURN_NUM(index); -} - -DEF_PRIMITIVE(string_iterateByte) -{ - ObjString* string = AS_STRING(args[0]); - - // If we're starting the iteration, return the first index. - if (IS_NULL(args[1])) - { - if (string->length == 0) RETURN_FALSE; - RETURN_NUM(0); - } - - if (!validateInt(vm, args[1], "Iterator")) return false; - - if (AS_NUM(args[1]) < 0) RETURN_FALSE; - uint32_t index = (uint32_t)AS_NUM(args[1]); - - // Advance to the next byte. - index++; - if (index >= string->length) RETURN_FALSE; - - RETURN_NUM(index); -} - -DEF_PRIMITIVE(string_iteratorValue) -{ - ObjString* string = AS_STRING(args[0]); - uint32_t index = validateIndex(vm, args[1], string->length, "Iterator"); - if (index == UINT32_MAX) return false; - - RETURN_VAL(wrenStringCodePointAt(vm, string, index)); -} - -DEF_PRIMITIVE(string_startsWith) -{ - if (!validateString(vm, args[1], "Argument")) return false; - - ObjString* string = AS_STRING(args[0]); - ObjString* search = AS_STRING(args[1]); - - // Edge case: If the search string is longer then return false right away. - if (search->length > string->length) RETURN_FALSE; - - RETURN_BOOL(memcmp(string->value, search->value, search->length) == 0); -} - -DEF_PRIMITIVE(string_plus) -{ - if (!validateString(vm, args[1], "Right operand")) return false; - RETURN_VAL(wrenStringFormat(vm, "@@", args[0], args[1])); -} - -DEF_PRIMITIVE(string_subscript) -{ - ObjString* string = AS_STRING(args[0]); - - if (IS_NUM(args[1])) - { - int index = validateIndex(vm, args[1], string->length, "Subscript"); - if (index == -1) return false; - - RETURN_VAL(wrenStringCodePointAt(vm, string, index)); - } - - if (!IS_RANGE(args[1])) - { - RETURN_ERROR("Subscript must be a number or a range."); - } - - int step; - uint32_t count = string->length; - int start = calculateRange(vm, AS_RANGE(args[1]), &count, &step); - if (start == -1) return false; - - RETURN_VAL(wrenNewStringFromRange(vm, string, start, count, step)); -} - -DEF_PRIMITIVE(string_toString) -{ - RETURN_VAL(args[0]); -} - -DEF_PRIMITIVE(system_clock) -{ - RETURN_NUM((double)clock() / CLOCKS_PER_SEC); -} - -DEF_PRIMITIVE(system_gc) -{ - wrenCollectGarbage(vm); - RETURN_NULL; -} - -DEF_PRIMITIVE(system_writeString) -{ - if (vm->config.writeFn != NULL) - { - vm->config.writeFn(vm, AS_CSTRING(args[1])); - } - - RETURN_VAL(args[1]); -} - -// Creates either the Object or Class class in the core module with [name]. -static ObjClass* defineClass(WrenVM* vm, ObjModule* module, const char* name) -{ - ObjString* nameString = AS_STRING(wrenNewString(vm, name)); - wrenPushRoot(vm, (Obj*)nameString); - - ObjClass* classObj = wrenNewSingleClass(vm, 0, nameString); - - wrenDefineVariable(vm, module, name, nameString->length, OBJ_VAL(classObj), NULL); - - wrenPopRoot(vm); - return classObj; -} - -void wrenInitializeCore(WrenVM* vm) -{ - ObjModule* coreModule = wrenNewModule(vm, NULL); - wrenPushRoot(vm, (Obj*)coreModule); - - // The core module's key is null in the module map. - wrenMapSet(vm, vm->modules, NULL_VAL, OBJ_VAL(coreModule)); - wrenPopRoot(vm); // coreModule. - - // Define the root Object class. This has to be done a little specially - // because it has no superclass. - vm->objectClass = defineClass(vm, coreModule, "Object"); - PRIMITIVE(vm->objectClass, "!", object_not); - PRIMITIVE(vm->objectClass, "==(_)", object_eqeq); - PRIMITIVE(vm->objectClass, "!=(_)", object_bangeq); - PRIMITIVE(vm->objectClass, "is(_)", object_is); - PRIMITIVE(vm->objectClass, "toString", object_toString); - PRIMITIVE(vm->objectClass, "type", object_type); - - // Now we can define Class, which is a subclass of Object. - vm->classClass = defineClass(vm, coreModule, "Class"); - wrenBindSuperclass(vm, vm->classClass, vm->objectClass); - PRIMITIVE(vm->classClass, "name", class_name); - PRIMITIVE(vm->classClass, "supertype", class_supertype); - PRIMITIVE(vm->classClass, "toString", class_toString); - PRIMITIVE(vm->classClass, "attributes", class_attributes); - - // Finally, we can define Object's metaclass which is a subclass of Class. - ObjClass* objectMetaclass = defineClass(vm, coreModule, "Object metaclass"); - - // Wire up the metaclass relationships now that all three classes are built. - vm->objectClass->obj.classObj = objectMetaclass; - objectMetaclass->obj.classObj = vm->classClass; - vm->classClass->obj.classObj = vm->classClass; - - // Do this after wiring up the metaclasses so objectMetaclass doesn't get - // collected. - wrenBindSuperclass(vm, objectMetaclass, vm->classClass); - - PRIMITIVE(objectMetaclass, "same(_,_)", object_same); - - // The core class diagram ends up looking like this, where single lines point - // to a class's superclass, and double lines point to its metaclass: - // - // .------------------------------------. .====. - // | .---------------. | # # - // v | v | v # - // .---------. .-------------------. .-------. # - // | Object |==>| Object metaclass |==>| Class |==" - // '---------' '-------------------' '-------' - // ^ ^ ^ ^ ^ - // | .--------------' # | # - // | | # | # - // .---------. .-------------------. # | # -. - // | Base |==>| Base metaclass |======" | # | - // '---------' '-------------------' | # | - // ^ | # | - // | .------------------' # | Example classes - // | | # | - // .---------. .-------------------. # | - // | Derived |==>| Derived metaclass |==========" | - // '---------' '-------------------' -' - - // The rest of the classes can now be defined normally. - wrenInterpret(vm, NULL, coreModuleSource); - - vm->boolClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Bool")); - PRIMITIVE(vm->boolClass, "toString", bool_toString); - PRIMITIVE(vm->boolClass, "!", bool_not); - - vm->fiberClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Fiber")); - PRIMITIVE(vm->fiberClass->obj.classObj, "new(_)", fiber_new); - PRIMITIVE(vm->fiberClass->obj.classObj, "abort(_)", fiber_abort); - PRIMITIVE(vm->fiberClass->obj.classObj, "current", fiber_current); - PRIMITIVE(vm->fiberClass->obj.classObj, "suspend()", fiber_suspend); - PRIMITIVE(vm->fiberClass->obj.classObj, "yield()", fiber_yield); - PRIMITIVE(vm->fiberClass->obj.classObj, "yield(_)", fiber_yield1); - PRIMITIVE(vm->fiberClass, "call()", fiber_call); - PRIMITIVE(vm->fiberClass, "call(_)", fiber_call1); - PRIMITIVE(vm->fiberClass, "error", fiber_error); - PRIMITIVE(vm->fiberClass, "isDone", fiber_isDone); - PRIMITIVE(vm->fiberClass, "transfer()", fiber_transfer); - PRIMITIVE(vm->fiberClass, "transfer(_)", fiber_transfer1); - PRIMITIVE(vm->fiberClass, "transferError(_)", fiber_transferError); - PRIMITIVE(vm->fiberClass, "try()", fiber_try); - PRIMITIVE(vm->fiberClass, "try(_)", fiber_try1); - - vm->fnClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Fn")); - PRIMITIVE(vm->fnClass->obj.classObj, "new(_)", fn_new); - - PRIMITIVE(vm->fnClass, "arity", fn_arity); - - FUNCTION_CALL(vm->fnClass, "call()", fn_call0); - FUNCTION_CALL(vm->fnClass, "call(_)", fn_call1); - FUNCTION_CALL(vm->fnClass, "call(_,_)", fn_call2); - FUNCTION_CALL(vm->fnClass, "call(_,_,_)", fn_call3); - FUNCTION_CALL(vm->fnClass, "call(_,_,_,_)", fn_call4); - FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_)", fn_call5); - FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_)", fn_call6); - FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_)", fn_call7); - FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_)", fn_call8); - FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_)", fn_call9); - FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_)", fn_call10); - FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_)", fn_call11); - FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_)", fn_call12); - FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call13); - FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call14); - FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call15); - FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call16); - - PRIMITIVE(vm->fnClass, "toString", fn_toString); - - vm->nullClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Null")); - PRIMITIVE(vm->nullClass, "!", null_not); - PRIMITIVE(vm->nullClass, "toString", null_toString); - - vm->numClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Num")); - PRIMITIVE(vm->numClass->obj.classObj, "fromString(_)", num_fromString); - PRIMITIVE(vm->numClass->obj.classObj, "infinity", num_infinity); - PRIMITIVE(vm->numClass->obj.classObj, "nan", num_nan); - PRIMITIVE(vm->numClass->obj.classObj, "pi", num_pi); - PRIMITIVE(vm->numClass->obj.classObj, "tau", num_tau); - PRIMITIVE(vm->numClass->obj.classObj, "largest", num_largest); - PRIMITIVE(vm->numClass->obj.classObj, "smallest", num_smallest); - PRIMITIVE(vm->numClass->obj.classObj, "maxSafeInteger", num_maxSafeInteger); - PRIMITIVE(vm->numClass->obj.classObj, "minSafeInteger", num_minSafeInteger); - PRIMITIVE(vm->numClass, "-(_)", num_minus); - PRIMITIVE(vm->numClass, "+(_)", num_plus); - PRIMITIVE(vm->numClass, "*(_)", num_multiply); - PRIMITIVE(vm->numClass, "/(_)", num_divide); - PRIMITIVE(vm->numClass, "<(_)", num_lt); - PRIMITIVE(vm->numClass, ">(_)", num_gt); - PRIMITIVE(vm->numClass, "<=(_)", num_lte); - PRIMITIVE(vm->numClass, ">=(_)", num_gte); - PRIMITIVE(vm->numClass, "&(_)", num_bitwiseAnd); - PRIMITIVE(vm->numClass, "|(_)", num_bitwiseOr); - PRIMITIVE(vm->numClass, "^(_)", num_bitwiseXor); - PRIMITIVE(vm->numClass, "<<(_)", num_bitwiseLeftShift); - PRIMITIVE(vm->numClass, ">>(_)", num_bitwiseRightShift); - PRIMITIVE(vm->numClass, "abs", num_abs); - PRIMITIVE(vm->numClass, "acos", num_acos); - PRIMITIVE(vm->numClass, "asin", num_asin); - PRIMITIVE(vm->numClass, "atan", num_atan); - PRIMITIVE(vm->numClass, "cbrt", num_cbrt); - PRIMITIVE(vm->numClass, "ceil", num_ceil); - PRIMITIVE(vm->numClass, "cos", num_cos); - PRIMITIVE(vm->numClass, "floor", num_floor); - PRIMITIVE(vm->numClass, "-", num_negate); - PRIMITIVE(vm->numClass, "round", num_round); - PRIMITIVE(vm->numClass, "min(_)", num_min); - PRIMITIVE(vm->numClass, "max(_)", num_max); - PRIMITIVE(vm->numClass, "clamp(_,_)", num_clamp); - PRIMITIVE(vm->numClass, "sin", num_sin); - PRIMITIVE(vm->numClass, "sqrt", num_sqrt); - PRIMITIVE(vm->numClass, "tan", num_tan); - PRIMITIVE(vm->numClass, "log", num_log); - PRIMITIVE(vm->numClass, "log2", num_log2); - PRIMITIVE(vm->numClass, "exp", num_exp); - PRIMITIVE(vm->numClass, "%(_)", num_mod); - PRIMITIVE(vm->numClass, "~", num_bitwiseNot); - PRIMITIVE(vm->numClass, "..(_)", num_dotDot); - PRIMITIVE(vm->numClass, "...(_)", num_dotDotDot); - PRIMITIVE(vm->numClass, "atan(_)", num_atan2); - PRIMITIVE(vm->numClass, "pow(_)", num_pow); - PRIMITIVE(vm->numClass, "fraction", num_fraction); - PRIMITIVE(vm->numClass, "isInfinity", num_isInfinity); - PRIMITIVE(vm->numClass, "isInteger", num_isInteger); - PRIMITIVE(vm->numClass, "isNan", num_isNan); - PRIMITIVE(vm->numClass, "sign", num_sign); - PRIMITIVE(vm->numClass, "toString", num_toString); - PRIMITIVE(vm->numClass, "truncate", num_truncate); - - // These are defined just so that 0 and -0 are equal, which is specified by - // IEEE 754 even though they have different bit representations. - PRIMITIVE(vm->numClass, "==(_)", num_eqeq); - PRIMITIVE(vm->numClass, "!=(_)", num_bangeq); - - vm->stringClass = AS_CLASS(wrenFindVariable(vm, coreModule, "String")); - PRIMITIVE(vm->stringClass->obj.classObj, "fromCodePoint(_)", string_fromCodePoint); - PRIMITIVE(vm->stringClass->obj.classObj, "fromByte(_)", string_fromByte); - PRIMITIVE(vm->stringClass, "+(_)", string_plus); - PRIMITIVE(vm->stringClass, "[_]", string_subscript); - PRIMITIVE(vm->stringClass, "byteAt_(_)", string_byteAt); - PRIMITIVE(vm->stringClass, "byteCount_", string_byteCount); - PRIMITIVE(vm->stringClass, "codePointAt_(_)", string_codePointAt); - PRIMITIVE(vm->stringClass, "contains(_)", string_contains); - PRIMITIVE(vm->stringClass, "endsWith(_)", string_endsWith); - PRIMITIVE(vm->stringClass, "indexOf(_)", string_indexOf1); - PRIMITIVE(vm->stringClass, "indexOf(_,_)", string_indexOf2); - PRIMITIVE(vm->stringClass, "iterate(_)", string_iterate); - PRIMITIVE(vm->stringClass, "iterateByte_(_)", string_iterateByte); - PRIMITIVE(vm->stringClass, "iteratorValue(_)", string_iteratorValue); - PRIMITIVE(vm->stringClass, "startsWith(_)", string_startsWith); - PRIMITIVE(vm->stringClass, "toString", string_toString); - - vm->listClass = AS_CLASS(wrenFindVariable(vm, coreModule, "List")); - PRIMITIVE(vm->listClass->obj.classObj, "filled(_,_)", list_filled); - PRIMITIVE(vm->listClass->obj.classObj, "new()", list_new); - PRIMITIVE(vm->listClass, "[_]", list_subscript); - PRIMITIVE(vm->listClass, "[_]=(_)", list_subscriptSetter); - PRIMITIVE(vm->listClass, "add(_)", list_add); - PRIMITIVE(vm->listClass, "addCore_(_)", list_addCore); - PRIMITIVE(vm->listClass, "clear()", list_clear); - PRIMITIVE(vm->listClass, "count", list_count); - PRIMITIVE(vm->listClass, "insert(_,_)", list_insert); - PRIMITIVE(vm->listClass, "iterate(_)", list_iterate); - PRIMITIVE(vm->listClass, "iteratorValue(_)", list_iteratorValue); - PRIMITIVE(vm->listClass, "removeAt(_)", list_removeAt); - PRIMITIVE(vm->listClass, "remove(_)", list_removeValue); - PRIMITIVE(vm->listClass, "indexOf(_)", list_indexOf); - PRIMITIVE(vm->listClass, "swap(_,_)", list_swap); - - vm->mapClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Map")); - PRIMITIVE(vm->mapClass->obj.classObj, "new()", map_new); - PRIMITIVE(vm->mapClass, "[_]", map_subscript); - PRIMITIVE(vm->mapClass, "[_]=(_)", map_subscriptSetter); - PRIMITIVE(vm->mapClass, "addCore_(_,_)", map_addCore); - PRIMITIVE(vm->mapClass, "clear()", map_clear); - PRIMITIVE(vm->mapClass, "containsKey(_)", map_containsKey); - PRIMITIVE(vm->mapClass, "count", map_count); - PRIMITIVE(vm->mapClass, "remove(_)", map_remove); - PRIMITIVE(vm->mapClass, "iterate(_)", map_iterate); - PRIMITIVE(vm->mapClass, "keyIteratorValue_(_)", map_keyIteratorValue); - PRIMITIVE(vm->mapClass, "valueIteratorValue_(_)", map_valueIteratorValue); - - vm->rangeClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Range")); - PRIMITIVE(vm->rangeClass, "from", range_from); - PRIMITIVE(vm->rangeClass, "to", range_to); - PRIMITIVE(vm->rangeClass, "min", range_min); - PRIMITIVE(vm->rangeClass, "max", range_max); - PRIMITIVE(vm->rangeClass, "isInclusive", range_isInclusive); - PRIMITIVE(vm->rangeClass, "iterate(_)", range_iterate); - PRIMITIVE(vm->rangeClass, "iteratorValue(_)", range_iteratorValue); - PRIMITIVE(vm->rangeClass, "toString", range_toString); - - ObjClass* systemClass = AS_CLASS(wrenFindVariable(vm, coreModule, "System")); - PRIMITIVE(systemClass->obj.classObj, "clock", system_clock); - PRIMITIVE(systemClass->obj.classObj, "gc()", system_gc); - PRIMITIVE(systemClass->obj.classObj, "writeString_(_)", system_writeString); - - // While bootstrapping the core types and running the core module, a number - // of string objects have been created, many of which were instantiated - // before stringClass was stored in the VM. Some of them *must* be created - // first -- the ObjClass for string itself has a reference to the ObjString - // for its name. - // - // These all currently have a NULL classObj pointer, so go back and assign - // them now that the string class is known. - for (Obj* obj = vm->first; obj != NULL; obj = obj->next) - { - if (obj->type == OBJ_STRING) obj->classObj = vm->stringClass; - } -} -// End file "wren_core.c" -// Begin file "wren_value.c" -#include -#include -#include -#include - - -#if WREN_DEBUG_TRACE_MEMORY -#endif - -// TODO: Tune these. -// The initial (and minimum) capacity of a non-empty list or map object. -#define MIN_CAPACITY 16 - -// The rate at which a collection's capacity grows when the size exceeds the -// current capacity. The new capacity will be determined by *multiplying* the -// old capacity by this. Growing geometrically is necessary to ensure that -// adding to a collection has O(1) amortized complexity. -#define GROW_FACTOR 2 - -// The maximum percentage of map entries that can be filled before the map is -// grown. A lower load takes more memory but reduces collisions which makes -// lookup faster. -#define MAP_LOAD_PERCENT 75 - -// The number of call frames initially allocated when a fiber is created. Making -// this smaller makes fibers use less memory (at first) but spends more time -// reallocating when the call stack grows. -#define INITIAL_CALL_FRAMES 4 - -DEFINE_BUFFER(Value, Value); -DEFINE_BUFFER(Method, Method); - -static void initObj(WrenVM* vm, Obj* obj, ObjType type, ObjClass* classObj) -{ - obj->type = type; - obj->isDark = false; - obj->classObj = classObj; - obj->next = vm->first; - vm->first = obj; -} - -ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name) -{ - ObjClass* classObj = ALLOCATE(vm, ObjClass); - initObj(vm, &classObj->obj, OBJ_CLASS, NULL); - classObj->superclass = NULL; - classObj->numFields = numFields; - classObj->name = name; - classObj->attributes = NULL_VAL; - - wrenPushRoot(vm, (Obj*)classObj); - wrenMethodBufferInit(&classObj->methods); - wrenPopRoot(vm); - - return classObj; -} - -void wrenBindSuperclass(WrenVM* vm, ObjClass* subclass, ObjClass* superclass) -{ - ASSERT(superclass != NULL, "Must have superclass."); - - subclass->superclass = superclass; - - // Include the superclass in the total number of fields. - if (subclass->numFields != -1) - { - subclass->numFields += superclass->numFields; - } - else - { - ASSERT(superclass->numFields == 0, - "A foreign class cannot inherit from a class with fields."); - } - - // Inherit methods from its superclass. - for (int i = 0; i < superclass->methods.count; i++) - { - wrenBindMethod(vm, subclass, i, superclass->methods.data[i]); - } -} - -ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields, - ObjString* name) -{ - // Create the metaclass. - Value metaclassName = wrenStringFormat(vm, "@ metaclass", OBJ_VAL(name)); - wrenPushRoot(vm, AS_OBJ(metaclassName)); - - ObjClass* metaclass = wrenNewSingleClass(vm, 0, AS_STRING(metaclassName)); - metaclass->obj.classObj = vm->classClass; - - wrenPopRoot(vm); - - // Make sure the metaclass isn't collected when we allocate the class. - wrenPushRoot(vm, (Obj*)metaclass); - - // Metaclasses always inherit Class and do not parallel the non-metaclass - // hierarchy. - wrenBindSuperclass(vm, metaclass, vm->classClass); - - ObjClass* classObj = wrenNewSingleClass(vm, numFields, name); - - // Make sure the class isn't collected while the inherited methods are being - // bound. - wrenPushRoot(vm, (Obj*)classObj); - - classObj->obj.classObj = metaclass; - wrenBindSuperclass(vm, classObj, superclass); - - wrenPopRoot(vm); - wrenPopRoot(vm); - - return classObj; -} - -void wrenBindMethod(WrenVM* vm, ObjClass* classObj, int symbol, Method method) -{ - // Make sure the buffer is big enough to contain the symbol's index. - if (symbol >= classObj->methods.count) - { - Method noMethod; - noMethod.type = METHOD_NONE; - wrenMethodBufferFill(vm, &classObj->methods, noMethod, - symbol - classObj->methods.count + 1); - } - - classObj->methods.data[symbol] = method; -} - -ObjClosure* wrenNewClosure(WrenVM* vm, ObjFn* fn) -{ - ObjClosure* closure = ALLOCATE_FLEX(vm, ObjClosure, - ObjUpvalue*, fn->numUpvalues); - initObj(vm, &closure->obj, OBJ_CLOSURE, vm->fnClass); - - closure->fn = fn; - - // Clear the upvalue array. We need to do this in case a GC is triggered - // after the closure is created but before the upvalue array is populated. - for (int i = 0; i < fn->numUpvalues; i++) closure->upvalues[i] = NULL; - - return closure; -} - -ObjFiber* wrenNewFiber(WrenVM* vm, ObjClosure* closure) -{ - // Allocate the arrays before the fiber in case it triggers a GC. - CallFrame* frames = ALLOCATE_ARRAY(vm, CallFrame, INITIAL_CALL_FRAMES); - - // Add one slot for the unused implicit receiver slot that the compiler - // assumes all functions have. - int stackCapacity = closure == NULL - ? 1 - : wrenPowerOf2Ceil(closure->fn->maxSlots + 1); - Value* stack = ALLOCATE_ARRAY(vm, Value, stackCapacity); - - ObjFiber* fiber = ALLOCATE(vm, ObjFiber); - initObj(vm, &fiber->obj, OBJ_FIBER, vm->fiberClass); - - fiber->stack = stack; - fiber->stackTop = fiber->stack; - fiber->stackCapacity = stackCapacity; - - fiber->frames = frames; - fiber->frameCapacity = INITIAL_CALL_FRAMES; - fiber->numFrames = 0; - - fiber->openUpvalues = NULL; - fiber->caller = NULL; - fiber->error = NULL_VAL; - fiber->state = FIBER_OTHER; - - if (closure != NULL) - { - // Initialize the first call frame. - wrenAppendCallFrame(vm, fiber, closure, fiber->stack); - - // The first slot always holds the closure. - fiber->stackTop[0] = OBJ_VAL(closure); - fiber->stackTop++; - } - - return fiber; -} - -void wrenEnsureStack(WrenVM* vm, ObjFiber* fiber, int needed) -{ - if (fiber->stackCapacity >= needed) return; - - int capacity = wrenPowerOf2Ceil(needed); - - Value* oldStack = fiber->stack; - fiber->stack = (Value*)wrenReallocate(vm, fiber->stack, - sizeof(Value) * fiber->stackCapacity, - sizeof(Value) * capacity); - fiber->stackCapacity = capacity; - - // If the reallocation moves the stack, then we need to recalculate every - // pointer that points into the old stack to into the same relative distance - // in the new stack. We have to be a little careful about how these are - // calculated because pointer subtraction is only well-defined within a - // single array, hence the slightly redundant-looking arithmetic below. - if (fiber->stack != oldStack) - { - // Top of the stack. - if (vm->apiStack >= oldStack && vm->apiStack <= fiber->stackTop) - { - vm->apiStack = fiber->stack + (vm->apiStack - oldStack); - } - - // Stack pointer for each call frame. - for (int i = 0; i < fiber->numFrames; i++) - { - CallFrame* frame = &fiber->frames[i]; - frame->stackStart = fiber->stack + (frame->stackStart - oldStack); - } - - // Open upvalues. - for (ObjUpvalue* upvalue = fiber->openUpvalues; - upvalue != NULL; - upvalue = upvalue->next) - { - upvalue->value = fiber->stack + (upvalue->value - oldStack); - } - - fiber->stackTop = fiber->stack + (fiber->stackTop - oldStack); - } -} - -ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size) -{ - ObjForeign* object = ALLOCATE_FLEX(vm, ObjForeign, uint8_t, size); - initObj(vm, &object->obj, OBJ_FOREIGN, classObj); - - // Zero out the bytes. - memset(object->data, 0, size); - return object; -} - -ObjFn* wrenNewFunction(WrenVM* vm, ObjModule* module, int maxSlots) -{ - FnDebug* debug = ALLOCATE(vm, FnDebug); - debug->name = NULL; - wrenIntBufferInit(&debug->sourceLines); - - ObjFn* fn = ALLOCATE(vm, ObjFn); - initObj(vm, &fn->obj, OBJ_FN, vm->fnClass); - - wrenValueBufferInit(&fn->constants); - wrenByteBufferInit(&fn->code); - fn->module = module; - fn->maxSlots = maxSlots; - fn->numUpvalues = 0; - fn->arity = 0; - fn->debug = debug; - - return fn; -} - -void wrenFunctionBindName(WrenVM* vm, ObjFn* fn, const char* name, int length) -{ - fn->debug->name = ALLOCATE_ARRAY(vm, char, length + 1); - memcpy(fn->debug->name, name, length); - fn->debug->name[length] = '\0'; -} - -Value wrenNewInstance(WrenVM* vm, ObjClass* classObj) -{ - ObjInstance* instance = ALLOCATE_FLEX(vm, ObjInstance, - Value, classObj->numFields); - initObj(vm, &instance->obj, OBJ_INSTANCE, classObj); - - // Initialize fields to null. - for (int i = 0; i < classObj->numFields; i++) - { - instance->fields[i] = NULL_VAL; - } - - return OBJ_VAL(instance); -} - -ObjList* wrenNewList(WrenVM* vm, uint32_t numElements) -{ - // Allocate this before the list object in case it triggers a GC which would - // free the list. - Value* elements = NULL; - if (numElements > 0) - { - elements = ALLOCATE_ARRAY(vm, Value, numElements); - } - - ObjList* list = ALLOCATE(vm, ObjList); - initObj(vm, &list->obj, OBJ_LIST, vm->listClass); - list->elements.capacity = numElements; - list->elements.count = numElements; - list->elements.data = elements; - return list; -} - -void wrenListInsert(WrenVM* vm, ObjList* list, Value value, uint32_t index) -{ - if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value)); - - // Add a slot at the end of the list. - wrenValueBufferWrite(vm, &list->elements, NULL_VAL); - - if (IS_OBJ(value)) wrenPopRoot(vm); - - // Shift the existing elements down. - for (uint32_t i = list->elements.count - 1; i > index; i--) - { - list->elements.data[i] = list->elements.data[i - 1]; - } - - // Store the new element. - list->elements.data[index] = value; -} - -int wrenListIndexOf(WrenVM* vm, ObjList* list, Value value) -{ - int count = list->elements.count; - for (int i = 0; i < count; i++) - { - Value item = list->elements.data[i]; - if(wrenValuesEqual(item, value)) { - return i; - } - } - return -1; -} - -Value wrenListRemoveAt(WrenVM* vm, ObjList* list, uint32_t index) -{ - Value removed = list->elements.data[index]; - - if (IS_OBJ(removed)) wrenPushRoot(vm, AS_OBJ(removed)); - - // Shift items up. - for (int i = index; i < list->elements.count - 1; i++) - { - list->elements.data[i] = list->elements.data[i + 1]; - } - - // If we have too much excess capacity, shrink it. - if (list->elements.capacity / GROW_FACTOR >= list->elements.count) - { - list->elements.data = (Value*)wrenReallocate(vm, list->elements.data, - sizeof(Value) * list->elements.capacity, - sizeof(Value) * (list->elements.capacity / GROW_FACTOR)); - list->elements.capacity /= GROW_FACTOR; - } - - if (IS_OBJ(removed)) wrenPopRoot(vm); - - list->elements.count--; - return removed; -} - -ObjMap* wrenNewMap(WrenVM* vm) -{ - ObjMap* map = ALLOCATE(vm, ObjMap); - initObj(vm, &map->obj, OBJ_MAP, vm->mapClass); - map->capacity = 0; - map->count = 0; - map->entries = NULL; - return map; -} - -static inline uint32_t hashBits(uint64_t hash) -{ - // From v8's ComputeLongHash() which in turn cites: - // Thomas Wang, Integer Hash Functions. - // http://www.concentric.net/~Ttwang/tech/inthash.htm - hash = ~hash + (hash << 18); // hash = (hash << 18) - hash - 1; - hash = hash ^ (hash >> 31); - hash = hash * 21; // hash = (hash + (hash << 2)) + (hash << 4); - hash = hash ^ (hash >> 11); - hash = hash + (hash << 6); - hash = hash ^ (hash >> 22); - return (uint32_t)(hash & 0x3fffffff); -} - -// Generates a hash code for [num]. -static inline uint32_t hashNumber(double num) -{ - // Hash the raw bits of the value. - return hashBits(wrenDoubleToBits(num)); -} - -// Generates a hash code for [object]. -static uint32_t hashObject(Obj* object) -{ - switch (object->type) - { - case OBJ_CLASS: - // Classes just use their name. - return hashObject((Obj*)((ObjClass*)object)->name); - - // Allow bare (non-closure) functions so that we can use a map to find - // existing constants in a function's constant table. This is only used - // internally. Since user code never sees a non-closure function, they - // cannot use them as map keys. - case OBJ_FN: - { - ObjFn* fn = (ObjFn*)object; - return hashNumber(fn->arity) ^ hashNumber(fn->code.count); - } - - case OBJ_RANGE: - { - ObjRange* range = (ObjRange*)object; - return hashNumber(range->from) ^ hashNumber(range->to); - } - - case OBJ_STRING: - return ((ObjString*)object)->hash; - - default: - ASSERT(false, "Only immutable objects can be hashed."); - return 0; - } -} - -// Generates a hash code for [value], which must be one of the built-in -// immutable types: null, bool, class, num, range, or string. -static uint32_t hashValue(Value value) -{ - // TODO: We'll probably want to randomize this at some point. - -#if WREN_NAN_TAGGING - if (IS_OBJ(value)) return hashObject(AS_OBJ(value)); - - // Hash the raw bits of the unboxed value. - return hashBits(value); -#else - switch (value.type) - { - case VAL_FALSE: return 0; - case VAL_NULL: return 1; - case VAL_NUM: return hashNumber(AS_NUM(value)); - case VAL_TRUE: return 2; - case VAL_OBJ: return hashObject(AS_OBJ(value)); - default: UNREACHABLE(); - } - - return 0; -#endif -} - -// Looks for an entry with [key] in an array of [capacity] [entries]. -// -// If found, sets [result] to point to it and returns `true`. Otherwise, -// returns `false` and points [result] to the entry where the key/value pair -// should be inserted. -static bool findEntry(MapEntry* entries, uint32_t capacity, Value key, - MapEntry** result) -{ - // If there is no entry array (an empty map), we definitely won't find it. - if (capacity == 0) return false; - - // Figure out where to insert it in the table. Use open addressing and - // basic linear probing. - uint32_t startIndex = hashValue(key) % capacity; - uint32_t index = startIndex; - - // If we pass a tombstone and don't end up finding the key, its entry will - // be re-used for the insert. - MapEntry* tombstone = NULL; - - // Walk the probe sequence until we've tried every slot. - do - { - MapEntry* entry = &entries[index]; - - if (IS_UNDEFINED(entry->key)) - { - // If we found an empty slot, the key is not in the table. If we found a - // slot that contains a deleted key, we have to keep looking. - if (IS_FALSE(entry->value)) - { - // We found an empty slot, so we've reached the end of the probe - // sequence without finding the key. If we passed a tombstone, then - // that's where we should insert the item, otherwise, put it here at - // the end of the sequence. - *result = tombstone != NULL ? tombstone : entry; - return false; - } - else - { - // We found a tombstone. We need to keep looking in case the key is - // after it, but we'll use this entry as the insertion point if the - // key ends up not being found. - if (tombstone == NULL) tombstone = entry; - } - } - else if (wrenValuesEqual(entry->key, key)) - { - // We found the key. - *result = entry; - return true; - } - - // Try the next slot. - index = (index + 1) % capacity; - } - while (index != startIndex); - - // If we get here, the table is full of tombstones. Return the first one we - // found. - ASSERT(tombstone != NULL, "Map should have tombstones or empty entries."); - *result = tombstone; - return false; -} - -// Inserts [key] and [value] in the array of [entries] with the given -// [capacity]. -// -// Returns `true` if this is the first time [key] was added to the map. -static bool insertEntry(MapEntry* entries, uint32_t capacity, - Value key, Value value) -{ - ASSERT(entries != NULL, "Should ensure capacity before inserting."); - - MapEntry* entry; - if (findEntry(entries, capacity, key, &entry)) - { - // Already present, so just replace the value. - entry->value = value; - return false; - } - else - { - entry->key = key; - entry->value = value; - return true; - } -} - -// Updates [map]'s entry array to [capacity]. -static void resizeMap(WrenVM* vm, ObjMap* map, uint32_t capacity) -{ - // Create the new empty hash table. - MapEntry* entries = ALLOCATE_ARRAY(vm, MapEntry, capacity); - for (uint32_t i = 0; i < capacity; i++) - { - entries[i].key = UNDEFINED_VAL; - entries[i].value = FALSE_VAL; - } - - // Re-add the existing entries. - if (map->capacity > 0) - { - for (uint32_t i = 0; i < map->capacity; i++) - { - MapEntry* entry = &map->entries[i]; - - // Don't copy empty entries or tombstones. - if (IS_UNDEFINED(entry->key)) continue; - - insertEntry(entries, capacity, entry->key, entry->value); - } - } - - // Replace the array. - DEALLOCATE(vm, map->entries); - map->entries = entries; - map->capacity = capacity; -} - -Value wrenMapGet(ObjMap* map, Value key) -{ - MapEntry* entry; - if (findEntry(map->entries, map->capacity, key, &entry)) return entry->value; - - return UNDEFINED_VAL; -} - -void wrenMapSet(WrenVM* vm, ObjMap* map, Value key, Value value) -{ - // If the map is getting too full, make room first. - if (map->count + 1 > map->capacity * MAP_LOAD_PERCENT / 100) - { - // Figure out the new hash table size. - uint32_t capacity = map->capacity * GROW_FACTOR; - if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY; - - resizeMap(vm, map, capacity); - } - - if (insertEntry(map->entries, map->capacity, key, value)) - { - // A new key was added. - map->count++; - } -} - -void wrenMapClear(WrenVM* vm, ObjMap* map) -{ - DEALLOCATE(vm, map->entries); - map->entries = NULL; - map->capacity = 0; - map->count = 0; -} - -Value wrenMapRemoveKey(WrenVM* vm, ObjMap* map, Value key) -{ - MapEntry* entry; - if (!findEntry(map->entries, map->capacity, key, &entry)) return NULL_VAL; - - // Remove the entry from the map. Set this value to true, which marks it as a - // deleted slot. When searching for a key, we will stop on empty slots, but - // continue past deleted slots. - Value value = entry->value; - entry->key = UNDEFINED_VAL; - entry->value = TRUE_VAL; - - if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value)); - - map->count--; - - if (map->count == 0) - { - // Removed the last item, so free the array. - wrenMapClear(vm, map); - } - else if (map->capacity > MIN_CAPACITY && - map->count < map->capacity / GROW_FACTOR * MAP_LOAD_PERCENT / 100) - { - uint32_t capacity = map->capacity / GROW_FACTOR; - if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY; - - // The map is getting empty, so shrink the entry array back down. - // TODO: Should we do this less aggressively than we grow? - resizeMap(vm, map, capacity); - } - - if (IS_OBJ(value)) wrenPopRoot(vm); - return value; -} - -ObjModule* wrenNewModule(WrenVM* vm, ObjString* name) -{ - ObjModule* module = ALLOCATE(vm, ObjModule); - - // Modules are never used as first-class objects, so don't need a class. - initObj(vm, (Obj*)module, OBJ_MODULE, NULL); - - wrenPushRoot(vm, (Obj*)module); - - wrenSymbolTableInit(&module->variableNames); - wrenValueBufferInit(&module->variables); - - module->name = name; - - wrenPopRoot(vm); - return module; -} - -Value wrenNewRange(WrenVM* vm, double from, double to, bool isInclusive) -{ - ObjRange* range = ALLOCATE(vm, ObjRange); - initObj(vm, &range->obj, OBJ_RANGE, vm->rangeClass); - range->from = from; - range->to = to; - range->isInclusive = isInclusive; - - return OBJ_VAL(range); -} - -// Creates a new string object with a null-terminated buffer large enough to -// hold a string of [length] but does not fill in the bytes. -// -// The caller is expected to fill in the buffer and then calculate the string's -// hash. -static ObjString* allocateString(WrenVM* vm, size_t length) -{ - ObjString* string = ALLOCATE_FLEX(vm, ObjString, char, length + 1); - initObj(vm, &string->obj, OBJ_STRING, vm->stringClass); - string->length = (int)length; - string->value[length] = '\0'; - - return string; -} - -// Calculates and stores the hash code for [string]. -static void hashString(ObjString* string) -{ - // FNV-1a hash. See: http://www.isthe.com/chongo/tech/comp/fnv/ - uint32_t hash = 2166136261u; - - // This is O(n) on the length of the string, but we only call this when a new - // string is created. Since the creation is also O(n) (to copy/initialize all - // the bytes), we allow this here. - for (uint32_t i = 0; i < string->length; i++) - { - hash ^= string->value[i]; - hash *= 16777619; - } - - string->hash = hash; -} - -Value wrenNewString(WrenVM* vm, const char* text) -{ - return wrenNewStringLength(vm, text, strlen(text)); -} - -Value wrenNewStringLength(WrenVM* vm, const char* text, size_t length) -{ - // Allow NULL if the string is empty since byte buffers don't allocate any - // characters for a zero-length string. - ASSERT(length == 0 || text != NULL, "Unexpected NULL string."); - - ObjString* string = allocateString(vm, length); - - // Copy the string (if given one). - if (length > 0 && text != NULL) memcpy(string->value, text, length); - - hashString(string); - return OBJ_VAL(string); -} - - -Value wrenNewStringFromRange(WrenVM* vm, ObjString* source, int start, - uint32_t count, int step) -{ - uint8_t* from = (uint8_t*)source->value; - int length = 0; - for (uint32_t i = 0; i < count; i++) - { - length += wrenUtf8DecodeNumBytes(from[start + i * step]); - } - - ObjString* result = allocateString(vm, length); - result->value[length] = '\0'; - - uint8_t* to = (uint8_t*)result->value; - for (uint32_t i = 0; i < count; i++) - { - int index = start + i * step; - int codePoint = wrenUtf8Decode(from + index, source->length - index); - - if (codePoint != -1) - { - to += wrenUtf8Encode(codePoint, to); - } - } - - hashString(result); - return OBJ_VAL(result); -} - -Value wrenNumToString(WrenVM* vm, double value) -{ - // Edge case: If the value is NaN or infinity, different versions of libc - // produce different outputs (some will format it signed and some won't). To - // get reliable output, handle it ourselves. - if (isnan(value)) return CONST_STRING(vm, "nan"); - if (isinf(value)) - { - if (value > 0.0) - { - return CONST_STRING(vm, "infinity"); - } - else - { - return CONST_STRING(vm, "-infinity"); - } - } - - // This is large enough to hold any double converted to a string using - // "%.14g". Example: - // - // -1.12345678901234e-1022 - // - // So we have: - // - // + 1 char for sign - // + 1 char for digit - // + 1 char for "." - // + 14 chars for decimal digits - // + 1 char for "e" - // + 1 char for "-" or "+" - // + 4 chars for exponent - // + 1 char for "\0" - // = 24 - char buffer[24]; - int length = sprintf(buffer, "%.14g", value); - return wrenNewStringLength(vm, buffer, length); -} - -Value wrenStringFromCodePoint(WrenVM* vm, int value) -{ - int length = wrenUtf8EncodeNumBytes(value); - ASSERT(length != 0, "Value out of range."); - - ObjString* string = allocateString(vm, length); - - wrenUtf8Encode(value, (uint8_t*)string->value); - hashString(string); - - return OBJ_VAL(string); -} - -Value wrenStringFromByte(WrenVM *vm, uint8_t value) -{ - int length = 1; - ObjString* string = allocateString(vm, length); - string->value[0] = value; - hashString(string); - return OBJ_VAL(string); -} - -Value wrenStringFormat(WrenVM* vm, const char* format, ...) -{ - va_list argList; - - // Calculate the length of the result string. Do this up front so we can - // create the final string with a single allocation. - va_start(argList, format); - size_t totalLength = 0; - for (const char* c = format; *c != '\0'; c++) - { - switch (*c) - { - case '$': - totalLength += strlen(va_arg(argList, const char*)); - break; - - case '@': - totalLength += AS_STRING(va_arg(argList, Value))->length; - break; - - default: - // Any other character is interpreted literally. - totalLength++; - } - } - va_end(argList); - - // Concatenate the string. - ObjString* result = allocateString(vm, totalLength); - - va_start(argList, format); - char* start = result->value; - for (const char* c = format; *c != '\0'; c++) - { - switch (*c) - { - case '$': - { - const char* string = va_arg(argList, const char*); - size_t length = strlen(string); - memcpy(start, string, length); - start += length; - break; - } - - case '@': - { - ObjString* string = AS_STRING(va_arg(argList, Value)); - memcpy(start, string->value, string->length); - start += string->length; - break; - } - - default: - // Any other character is interpreted literally. - *start++ = *c; - } - } - va_end(argList); - - hashString(result); - - return OBJ_VAL(result); -} - -Value wrenStringCodePointAt(WrenVM* vm, ObjString* string, uint32_t index) -{ - ASSERT(index < string->length, "Index out of bounds."); - - int codePoint = wrenUtf8Decode((uint8_t*)string->value + index, - string->length - index); - if (codePoint == -1) - { - // If it isn't a valid UTF-8 sequence, treat it as a single raw byte. - char bytes[2]; - bytes[0] = string->value[index]; - bytes[1] = '\0'; - return wrenNewStringLength(vm, bytes, 1); - } - - return wrenStringFromCodePoint(vm, codePoint); -} - -// Uses the Boyer-Moore-Horspool string matching algorithm. -uint32_t wrenStringFind(ObjString* haystack, ObjString* needle, uint32_t start) -{ - // Edge case: An empty needle is always found. - if (needle->length == 0) return start; - - // If the needle goes past the haystack it won't be found. - if (start + needle->length > haystack->length) return UINT32_MAX; - - // If the startIndex is too far it also won't be found. - if (start >= haystack->length) return UINT32_MAX; - - // Pre-calculate the shift table. For each character (8-bit value), we - // determine how far the search window can be advanced if that character is - // the last character in the haystack where we are searching for the needle - // and the needle doesn't match there. - uint32_t shift[UINT8_MAX]; - uint32_t needleEnd = needle->length - 1; - - // By default, we assume the character is not the needle at all. In that case - // case, if a match fails on that character, we can advance one whole needle - // width since. - for (uint32_t index = 0; index < UINT8_MAX; index++) - { - shift[index] = needle->length; - } - - // Then, for every character in the needle, determine how far it is from the - // end. If a match fails on that character, we can advance the window such - // that it the last character in it lines up with the last place we could - // find it in the needle. - for (uint32_t index = 0; index < needleEnd; index++) - { - char c = needle->value[index]; - shift[(uint8_t)c] = needleEnd - index; - } - - // Slide the needle across the haystack, looking for the first match or - // stopping if the needle goes off the end. - char lastChar = needle->value[needleEnd]; - uint32_t range = haystack->length - needle->length; - - for (uint32_t index = start; index <= range; ) - { - // Compare the last character in the haystack's window to the last character - // in the needle. If it matches, see if the whole needle matches. - char c = haystack->value[index + needleEnd]; - if (lastChar == c && - memcmp(haystack->value + index, needle->value, needleEnd) == 0) - { - // Found a match. - return index; - } - - // Otherwise, slide the needle forward. - index += shift[(uint8_t)c]; - } - - // Not found. - return UINT32_MAX; -} - -ObjUpvalue* wrenNewUpvalue(WrenVM* vm, Value* value) -{ - ObjUpvalue* upvalue = ALLOCATE(vm, ObjUpvalue); - - // Upvalues are never used as first-class objects, so don't need a class. - initObj(vm, &upvalue->obj, OBJ_UPVALUE, NULL); - - upvalue->value = value; - upvalue->closed = NULL_VAL; - upvalue->next = NULL; - return upvalue; -} - -void wrenGrayObj(WrenVM* vm, Obj* obj) -{ - if (obj == NULL) return; - - // Stop if the object is already darkened so we don't get stuck in a cycle. - if (obj->isDark) return; - - // It's been reached. - obj->isDark = true; - - // Add it to the gray list so it can be recursively explored for - // more marks later. - if (vm->grayCount >= vm->grayCapacity) - { - vm->grayCapacity = vm->grayCount * 2; - vm->gray = (Obj**)vm->config.reallocateFn(vm->gray, - vm->grayCapacity * sizeof(Obj*), - vm->config.userData); - } - - vm->gray[vm->grayCount++] = obj; -} - -void wrenGrayValue(WrenVM* vm, Value value) -{ - if (!IS_OBJ(value)) return; - wrenGrayObj(vm, AS_OBJ(value)); -} - -void wrenGrayBuffer(WrenVM* vm, ValueBuffer* buffer) -{ - for (int i = 0; i < buffer->count; i++) - { - wrenGrayValue(vm, buffer->data[i]); - } -} - -static void blackenClass(WrenVM* vm, ObjClass* classObj) -{ - // The metaclass. - wrenGrayObj(vm, (Obj*)classObj->obj.classObj); - - // The superclass. - wrenGrayObj(vm, (Obj*)classObj->superclass); - - // Method function objects. - for (int i = 0; i < classObj->methods.count; i++) - { - if (classObj->methods.data[i].type == METHOD_BLOCK) - { - wrenGrayObj(vm, (Obj*)classObj->methods.data[i].as.closure); - } - } - - wrenGrayObj(vm, (Obj*)classObj->name); - - if(!IS_NULL(classObj->attributes)) wrenGrayObj(vm, AS_OBJ(classObj->attributes)); - - // Keep track of how much memory is still in use. - vm->bytesAllocated += sizeof(ObjClass); - vm->bytesAllocated += classObj->methods.capacity * sizeof(Method); -} - -static void blackenClosure(WrenVM* vm, ObjClosure* closure) -{ - // Mark the function. - wrenGrayObj(vm, (Obj*)closure->fn); - - // Mark the upvalues. - for (int i = 0; i < closure->fn->numUpvalues; i++) - { - wrenGrayObj(vm, (Obj*)closure->upvalues[i]); - } - - // Keep track of how much memory is still in use. - vm->bytesAllocated += sizeof(ObjClosure); - vm->bytesAllocated += sizeof(ObjUpvalue*) * closure->fn->numUpvalues; -} - -static void blackenFiber(WrenVM* vm, ObjFiber* fiber) -{ - // Stack functions. - for (int i = 0; i < fiber->numFrames; i++) - { - wrenGrayObj(vm, (Obj*)fiber->frames[i].closure); - } - - // Stack variables. - for (Value* slot = fiber->stack; slot < fiber->stackTop; slot++) - { - wrenGrayValue(vm, *slot); - } - - // Open upvalues. - ObjUpvalue* upvalue = fiber->openUpvalues; - while (upvalue != NULL) - { - wrenGrayObj(vm, (Obj*)upvalue); - upvalue = upvalue->next; - } - - // The caller. - wrenGrayObj(vm, (Obj*)fiber->caller); - wrenGrayValue(vm, fiber->error); - - // Keep track of how much memory is still in use. - vm->bytesAllocated += sizeof(ObjFiber); - vm->bytesAllocated += fiber->frameCapacity * sizeof(CallFrame); - vm->bytesAllocated += fiber->stackCapacity * sizeof(Value); -} - -static void blackenFn(WrenVM* vm, ObjFn* fn) -{ - // Mark the constants. - wrenGrayBuffer(vm, &fn->constants); - - // Mark the module it belongs to, in case it's been unloaded. - wrenGrayObj(vm, (Obj*)fn->module); - - // Keep track of how much memory is still in use. - vm->bytesAllocated += sizeof(ObjFn); - vm->bytesAllocated += sizeof(uint8_t) * fn->code.capacity; - vm->bytesAllocated += sizeof(Value) * fn->constants.capacity; - - // The debug line number buffer. - vm->bytesAllocated += sizeof(int) * fn->code.capacity; - // TODO: What about the function name? -} - -static void blackenForeign(WrenVM* vm, ObjForeign* foreign) -{ - // TODO: Keep track of how much memory the foreign object uses. We can store - // this in each foreign object, but it will balloon the size. We may not want - // that much overhead. One option would be to let the foreign class register - // a C function that returns a size for the object. That way the VM doesn't - // always have to explicitly store it. -} - -static void blackenInstance(WrenVM* vm, ObjInstance* instance) -{ - wrenGrayObj(vm, (Obj*)instance->obj.classObj); - - // Mark the fields. - for (int i = 0; i < instance->obj.classObj->numFields; i++) - { - wrenGrayValue(vm, instance->fields[i]); - } - - // Keep track of how much memory is still in use. - vm->bytesAllocated += sizeof(ObjInstance); - vm->bytesAllocated += sizeof(Value) * instance->obj.classObj->numFields; -} - -static void blackenList(WrenVM* vm, ObjList* list) -{ - // Mark the elements. - wrenGrayBuffer(vm, &list->elements); - - // Keep track of how much memory is still in use. - vm->bytesAllocated += sizeof(ObjList); - vm->bytesAllocated += sizeof(Value) * list->elements.capacity; -} - -static void blackenMap(WrenVM* vm, ObjMap* map) -{ - // Mark the entries. - for (uint32_t i = 0; i < map->capacity; i++) - { - MapEntry* entry = &map->entries[i]; - if (IS_UNDEFINED(entry->key)) continue; - - wrenGrayValue(vm, entry->key); - wrenGrayValue(vm, entry->value); - } - - // Keep track of how much memory is still in use. - vm->bytesAllocated += sizeof(ObjMap); - vm->bytesAllocated += sizeof(MapEntry) * map->capacity; -} - -static void blackenModule(WrenVM* vm, ObjModule* module) -{ - // Top-level variables. - for (int i = 0; i < module->variables.count; i++) - { - wrenGrayValue(vm, module->variables.data[i]); - } - - wrenBlackenSymbolTable(vm, &module->variableNames); - - wrenGrayObj(vm, (Obj*)module->name); - - // Keep track of how much memory is still in use. - vm->bytesAllocated += sizeof(ObjModule); -} - -static void blackenRange(WrenVM* vm, ObjRange* range) -{ - // Keep track of how much memory is still in use. - vm->bytesAllocated += sizeof(ObjRange); -} - -static void blackenString(WrenVM* vm, ObjString* string) -{ - // Keep track of how much memory is still in use. - vm->bytesAllocated += sizeof(ObjString) + string->length + 1; -} - -static void blackenUpvalue(WrenVM* vm, ObjUpvalue* upvalue) -{ - // Mark the closed-over object (in case it is closed). - wrenGrayValue(vm, upvalue->closed); - - // Keep track of how much memory is still in use. - vm->bytesAllocated += sizeof(ObjUpvalue); -} - -static void blackenObject(WrenVM* vm, Obj* obj) -{ -#if WREN_DEBUG_TRACE_MEMORY - printf("mark "); - wrenDumpValue(OBJ_VAL(obj)); - printf(" @ %p\n", obj); -#endif - - // Traverse the object's fields. - switch (obj->type) - { - case OBJ_CLASS: blackenClass( vm, (ObjClass*) obj); break; - case OBJ_CLOSURE: blackenClosure( vm, (ObjClosure*) obj); break; - case OBJ_FIBER: blackenFiber( vm, (ObjFiber*) obj); break; - case OBJ_FN: blackenFn( vm, (ObjFn*) obj); break; - case OBJ_FOREIGN: blackenForeign( vm, (ObjForeign*) obj); break; - case OBJ_INSTANCE: blackenInstance(vm, (ObjInstance*)obj); break; - case OBJ_LIST: blackenList( vm, (ObjList*) obj); break; - case OBJ_MAP: blackenMap( vm, (ObjMap*) obj); break; - case OBJ_MODULE: blackenModule( vm, (ObjModule*) obj); break; - case OBJ_RANGE: blackenRange( vm, (ObjRange*) obj); break; - case OBJ_STRING: blackenString( vm, (ObjString*) obj); break; - case OBJ_UPVALUE: blackenUpvalue( vm, (ObjUpvalue*) obj); break; - } -} - -void wrenBlackenObjects(WrenVM* vm) -{ - while (vm->grayCount > 0) - { - // Pop an item from the gray stack. - Obj* obj = vm->gray[--vm->grayCount]; - blackenObject(vm, obj); - } -} - -void wrenFreeObj(WrenVM* vm, Obj* obj) -{ -#if WREN_DEBUG_TRACE_MEMORY - printf("free "); - wrenDumpValue(OBJ_VAL(obj)); - printf(" @ %p\n", obj); -#endif - - switch (obj->type) - { - case OBJ_CLASS: - wrenMethodBufferClear(vm, &((ObjClass*)obj)->methods); - break; - - case OBJ_FIBER: - { - ObjFiber* fiber = (ObjFiber*)obj; - DEALLOCATE(vm, fiber->frames); - DEALLOCATE(vm, fiber->stack); - break; - } - - case OBJ_FN: - { - ObjFn* fn = (ObjFn*)obj; - wrenValueBufferClear(vm, &fn->constants); - wrenByteBufferClear(vm, &fn->code); - wrenIntBufferClear(vm, &fn->debug->sourceLines); - DEALLOCATE(vm, fn->debug->name); - DEALLOCATE(vm, fn->debug); - break; - } - - case OBJ_FOREIGN: - wrenFinalizeForeign(vm, (ObjForeign*)obj); - break; - - case OBJ_LIST: - wrenValueBufferClear(vm, &((ObjList*)obj)->elements); - break; - - case OBJ_MAP: - DEALLOCATE(vm, ((ObjMap*)obj)->entries); - break; - - case OBJ_MODULE: - wrenSymbolTableClear(vm, &((ObjModule*)obj)->variableNames); - wrenValueBufferClear(vm, &((ObjModule*)obj)->variables); - break; - - case OBJ_CLOSURE: - case OBJ_INSTANCE: - case OBJ_RANGE: - case OBJ_STRING: - case OBJ_UPVALUE: - break; - } - - DEALLOCATE(vm, obj); -} - -ObjClass* wrenGetClass(WrenVM* vm, Value value) -{ - return wrenGetClassInline(vm, value); -} - -bool wrenValuesEqual(Value a, Value b) -{ - if (wrenValuesSame(a, b)) return true; - - // If we get here, it's only possible for two heap-allocated immutable objects - // to be equal. - if (!IS_OBJ(a) || !IS_OBJ(b)) return false; - - Obj* aObj = AS_OBJ(a); - Obj* bObj = AS_OBJ(b); - - // Must be the same type. - if (aObj->type != bObj->type) return false; - - switch (aObj->type) - { - case OBJ_RANGE: - { - ObjRange* aRange = (ObjRange*)aObj; - ObjRange* bRange = (ObjRange*)bObj; - return aRange->from == bRange->from && - aRange->to == bRange->to && - aRange->isInclusive == bRange->isInclusive; - } - - case OBJ_STRING: - { - ObjString* aString = (ObjString*)aObj; - ObjString* bString = (ObjString*)bObj; - return aString->hash == bString->hash && - wrenStringEqualsCString(aString, bString->value, bString->length); - } - - default: - // All other types are only equal if they are same, which they aren't if - // we get here. - return false; - } -} -// End file "wren_value.c" -// Begin file "wren_utils.c" -#include - - -DEFINE_BUFFER(Byte, uint8_t); -DEFINE_BUFFER(Int, int); -DEFINE_BUFFER(String, ObjString*); - -void wrenSymbolTableInit(SymbolTable* symbols) -{ - wrenStringBufferInit(symbols); -} - -void wrenSymbolTableClear(WrenVM* vm, SymbolTable* symbols) -{ - wrenStringBufferClear(vm, symbols); -} - -int wrenSymbolTableAdd(WrenVM* vm, SymbolTable* symbols, - const char* name, size_t length) -{ - ObjString* symbol = AS_STRING(wrenNewStringLength(vm, name, length)); - - wrenPushRoot(vm, &symbol->obj); - wrenStringBufferWrite(vm, symbols, symbol); - wrenPopRoot(vm); - - return symbols->count - 1; -} - -int wrenSymbolTableEnsure(WrenVM* vm, SymbolTable* symbols, - const char* name, size_t length) -{ - // See if the symbol is already defined. - int existing = wrenSymbolTableFind(symbols, name, length); - if (existing != -1) return existing; - - // New symbol, so add it. - return wrenSymbolTableAdd(vm, symbols, name, length); -} - -int wrenSymbolTableFind(const SymbolTable* symbols, - const char* name, size_t length) -{ - // See if the symbol is already defined. - // TODO: O(n). Do something better. - for (int i = 0; i < symbols->count; i++) - { - if (wrenStringEqualsCString(symbols->data[i], name, length)) return i; - } - - return -1; -} - -void wrenBlackenSymbolTable(WrenVM* vm, SymbolTable* symbolTable) -{ - for (int i = 0; i < symbolTable->count; i++) - { - wrenGrayObj(vm, &symbolTable->data[i]->obj); - } - - // Keep track of how much memory is still in use. - vm->bytesAllocated += symbolTable->capacity * sizeof(*symbolTable->data); -} - -int wrenUtf8EncodeNumBytes(int value) -{ - ASSERT(value >= 0, "Cannot encode a negative value."); - - if (value <= 0x7f) return 1; - if (value <= 0x7ff) return 2; - if (value <= 0xffff) return 3; - if (value <= 0x10ffff) return 4; - return 0; -} - -int wrenUtf8Encode(int value, uint8_t* bytes) -{ - if (value <= 0x7f) - { - // Single byte (i.e. fits in ASCII). - *bytes = value & 0x7f; - return 1; - } - else if (value <= 0x7ff) - { - // Two byte sequence: 110xxxxx 10xxxxxx. - *bytes = 0xc0 | ((value & 0x7c0) >> 6); - bytes++; - *bytes = 0x80 | (value & 0x3f); - return 2; - } - else if (value <= 0xffff) - { - // Three byte sequence: 1110xxxx 10xxxxxx 10xxxxxx. - *bytes = 0xe0 | ((value & 0xf000) >> 12); - bytes++; - *bytes = 0x80 | ((value & 0xfc0) >> 6); - bytes++; - *bytes = 0x80 | (value & 0x3f); - return 3; - } - else if (value <= 0x10ffff) - { - // Four byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx. - *bytes = 0xf0 | ((value & 0x1c0000) >> 18); - bytes++; - *bytes = 0x80 | ((value & 0x3f000) >> 12); - bytes++; - *bytes = 0x80 | ((value & 0xfc0) >> 6); - bytes++; - *bytes = 0x80 | (value & 0x3f); - return 4; - } - - // Invalid Unicode value. See: http://tools.ietf.org/html/rfc3629 - UNREACHABLE(); - return 0; -} - -int wrenUtf8Decode(const uint8_t* bytes, uint32_t length) -{ - // Single byte (i.e. fits in ASCII). - if (*bytes <= 0x7f) return *bytes; - - int value; - uint32_t remainingBytes; - if ((*bytes & 0xe0) == 0xc0) - { - // Two byte sequence: 110xxxxx 10xxxxxx. - value = *bytes & 0x1f; - remainingBytes = 1; - } - else if ((*bytes & 0xf0) == 0xe0) - { - // Three byte sequence: 1110xxxx 10xxxxxx 10xxxxxx. - value = *bytes & 0x0f; - remainingBytes = 2; - } - else if ((*bytes & 0xf8) == 0xf0) - { - // Four byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx. - value = *bytes & 0x07; - remainingBytes = 3; - } - else - { - // Invalid UTF-8 sequence. - return -1; - } - - // Don't read past the end of the buffer on truncated UTF-8. - if (remainingBytes > length - 1) return -1; - - while (remainingBytes > 0) - { - bytes++; - remainingBytes--; - - // Remaining bytes must be of form 10xxxxxx. - if ((*bytes & 0xc0) != 0x80) return -1; - - value = value << 6 | (*bytes & 0x3f); - } - - return value; -} - -int wrenUtf8DecodeNumBytes(uint8_t byte) -{ - // If the byte starts with 10xxxxx, it's the middle of a UTF-8 sequence, so - // don't count it at all. - if ((byte & 0xc0) == 0x80) return 0; - - // The first byte's high bits tell us how many bytes are in the UTF-8 - // sequence. - if ((byte & 0xf8) == 0xf0) return 4; - if ((byte & 0xf0) == 0xe0) return 3; - if ((byte & 0xe0) == 0xc0) return 2; - return 1; -} - -// From: http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2Float -int wrenPowerOf2Ceil(int n) -{ - n--; - n |= n >> 1; - n |= n >> 2; - n |= n >> 4; - n |= n >> 8; - n |= n >> 16; - n++; - - return n; -} - -uint32_t wrenValidateIndex(uint32_t count, int64_t value) -{ - // Negative indices count from the end. - if (value < 0) value = count + value; - - // Check bounds. - if (value >= 0 && value < count) return (uint32_t)value; - - return UINT32_MAX; -} -// End file "wren_utils.c" -// Begin file "wren_vm.c" -#include -#include - - -#if WREN_OPT_META -// Begin file "wren_opt_meta.h" -#ifndef wren_opt_meta_h -#define wren_opt_meta_h - - -// This module defines the Meta class and its associated methods. -#if WREN_OPT_META - -const char* wrenMetaSource(); -WrenForeignMethodFn wrenMetaBindForeignMethod(WrenVM* vm, - const char* className, - bool isStatic, - const char* signature); - -#endif - -#endif -// End file "wren_opt_meta.h" -#endif -#if WREN_OPT_RANDOM -// Begin file "wren_opt_random.h" -#ifndef wren_opt_random_h -#define wren_opt_random_h - - -#if WREN_OPT_RANDOM - -const char* wrenRandomSource(); -WrenForeignClassMethods wrenRandomBindForeignClass(WrenVM* vm, - const char* module, - const char* className); -WrenForeignMethodFn wrenRandomBindForeignMethod(WrenVM* vm, - const char* className, - bool isStatic, - const char* signature); - -#endif - -#endif -// End file "wren_opt_random.h" -#endif - -#if WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC - #include - #include -#endif - -// The behavior of realloc() when the size is 0 is implementation defined. It -// may return a non-NULL pointer which must not be dereferenced but nevertheless -// should be freed. To prevent that, we avoid calling realloc() with a zero -// size. -static void* defaultReallocate(void* ptr, size_t newSize, void* _) -{ - if (newSize == 0) - { - free(ptr); - return NULL; - } - - return realloc(ptr, newSize); -} - -int wrenGetVersionNumber() -{ - return WREN_VERSION_NUMBER; -} - -void wrenInitConfiguration(WrenConfiguration* config) -{ - config->reallocateFn = defaultReallocate; - config->resolveModuleFn = NULL; - config->loadModuleFn = NULL; - config->bindForeignMethodFn = NULL; - config->bindForeignClassFn = NULL; - config->writeFn = NULL; - config->errorFn = NULL; - config->initialHeapSize = 1024 * 1024 * 10; - config->minHeapSize = 1024 * 1024; - config->heapGrowthPercent = 50; - config->userData = NULL; -} - -WrenVM* wrenNewVM(WrenConfiguration* config) -{ - WrenReallocateFn reallocate = defaultReallocate; - void* userData = NULL; - if (config != NULL) { - userData = config->userData; - reallocate = config->reallocateFn ? config->reallocateFn : defaultReallocate; - } - - WrenVM* vm = (WrenVM*)reallocate(NULL, sizeof(*vm), userData); - memset(vm, 0, sizeof(WrenVM)); - - // Copy the configuration if given one. - if (config != NULL) - { - memcpy(&vm->config, config, sizeof(WrenConfiguration)); - - // We choose to set this after copying, - // rather than modifying the user config pointer - vm->config.reallocateFn = reallocate; - } - else - { - wrenInitConfiguration(&vm->config); - } - - // TODO: Should we allocate and free this during a GC? - vm->grayCount = 0; - // TODO: Tune this. - vm->grayCapacity = 4; - vm->gray = (Obj**)reallocate(NULL, vm->grayCapacity * sizeof(Obj*), userData); - vm->nextGC = vm->config.initialHeapSize; - - wrenSymbolTableInit(&vm->methodNames); - - vm->modules = wrenNewMap(vm); - wrenInitializeCore(vm); - return vm; -} - -void wrenFreeVM(WrenVM* vm) -{ - ASSERT(vm->methodNames.count > 0, "VM appears to have already been freed."); - - // Free all of the GC objects. - Obj* obj = vm->first; - while (obj != NULL) - { - Obj* next = obj->next; - wrenFreeObj(vm, obj); - obj = next; - } - - // Free up the GC gray set. - vm->gray = (Obj**)vm->config.reallocateFn(vm->gray, 0, vm->config.userData); - - // Tell the user if they didn't free any handles. We don't want to just free - // them here because the host app may still have pointers to them that they - // may try to use. Better to tell them about the bug early. - ASSERT(vm->handles == NULL, "All handles have not been released."); - - wrenSymbolTableClear(vm, &vm->methodNames); - - DEALLOCATE(vm, vm); -} - -void wrenCollectGarbage(WrenVM* vm) -{ -#if WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC - printf("-- gc --\n"); - - size_t before = vm->bytesAllocated; - double startTime = (double)clock() / CLOCKS_PER_SEC; -#endif - - // Mark all reachable objects. - - // Reset this. As we mark objects, their size will be counted again so that - // we can track how much memory is in use without needing to know the size - // of each *freed* object. - // - // This is important because when freeing an unmarked object, we don't always - // know how much memory it is using. For example, when freeing an instance, - // we need to know its class to know how big it is, but its class may have - // already been freed. - vm->bytesAllocated = 0; - - wrenGrayObj(vm, (Obj*)vm->modules); - - // Temporary roots. - for (int i = 0; i < vm->numTempRoots; i++) - { - wrenGrayObj(vm, vm->tempRoots[i]); - } - - // The current fiber. - wrenGrayObj(vm, (Obj*)vm->fiber); - - // The handles. - for (WrenHandle* handle = vm->handles; - handle != NULL; - handle = handle->next) - { - wrenGrayValue(vm, handle->value); - } - - // Any object the compiler is using (if there is one). - if (vm->compiler != NULL) wrenMarkCompiler(vm, vm->compiler); - - // Method names. - wrenBlackenSymbolTable(vm, &vm->methodNames); - - // Now that we have grayed the roots, do a depth-first search over all of the - // reachable objects. - wrenBlackenObjects(vm); - - // Collect the white objects. - Obj** obj = &vm->first; - while (*obj != NULL) - { - if (!((*obj)->isDark)) - { - // This object wasn't reached, so remove it from the list and free it. - Obj* unreached = *obj; - *obj = unreached->next; - wrenFreeObj(vm, unreached); - } - else - { - // This object was reached, so unmark it (for the next GC) and move on to - // the next. - (*obj)->isDark = false; - obj = &(*obj)->next; - } - } - - // Calculate the next gc point, this is the current allocation plus - // a configured percentage of the current allocation. - vm->nextGC = vm->bytesAllocated + ((vm->bytesAllocated * vm->config.heapGrowthPercent) / 100); - if (vm->nextGC < vm->config.minHeapSize) vm->nextGC = vm->config.minHeapSize; - -#if WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC - double elapsed = ((double)clock() / CLOCKS_PER_SEC) - startTime; - // Explicit cast because size_t has different sizes on 32-bit and 64-bit and - // we need a consistent type for the format string. - printf("GC %lu before, %lu after (%lu collected), next at %lu. Took %.3fms.\n", - (unsigned long)before, - (unsigned long)vm->bytesAllocated, - (unsigned long)(before - vm->bytesAllocated), - (unsigned long)vm->nextGC, - elapsed*1000.0); -#endif -} - -void* wrenReallocate(WrenVM* vm, void* memory, size_t oldSize, size_t newSize) -{ -#if WREN_DEBUG_TRACE_MEMORY - // Explicit cast because size_t has different sizes on 32-bit and 64-bit and - // we need a consistent type for the format string. - printf("reallocate %p %lu -> %lu\n", - memory, (unsigned long)oldSize, (unsigned long)newSize); -#endif - - // If new bytes are being allocated, add them to the total count. If objects - // are being completely deallocated, we don't track that (since we don't - // track the original size). Instead, that will be handled while marking - // during the next GC. - vm->bytesAllocated += newSize - oldSize; - -#if WREN_DEBUG_GC_STRESS - // Since collecting calls this function to free things, make sure we don't - // recurse. - if (newSize > 0) wrenCollectGarbage(vm); -#else - if (newSize > 0 && vm->bytesAllocated > vm->nextGC) wrenCollectGarbage(vm); -#endif - - return vm->config.reallocateFn(memory, newSize, vm->config.userData); -} - -// Captures the local variable [local] into an [Upvalue]. If that local is -// already in an upvalue, the existing one will be used. (This is important to -// ensure that multiple closures closing over the same variable actually see -// the same variable.) Otherwise, it will create a new open upvalue and add it -// the fiber's list of upvalues. -static ObjUpvalue* captureUpvalue(WrenVM* vm, ObjFiber* fiber, Value* local) -{ - // If there are no open upvalues at all, we must need a new one. - if (fiber->openUpvalues == NULL) - { - fiber->openUpvalues = wrenNewUpvalue(vm, local); - return fiber->openUpvalues; - } - - ObjUpvalue* prevUpvalue = NULL; - ObjUpvalue* upvalue = fiber->openUpvalues; - - // Walk towards the bottom of the stack until we find a previously existing - // upvalue or pass where it should be. - while (upvalue != NULL && upvalue->value > local) - { - prevUpvalue = upvalue; - upvalue = upvalue->next; - } - - // Found an existing upvalue for this local. - if (upvalue != NULL && upvalue->value == local) return upvalue; - - // We've walked past this local on the stack, so there must not be an - // upvalue for it already. Make a new one and link it in in the right - // place to keep the list sorted. - ObjUpvalue* createdUpvalue = wrenNewUpvalue(vm, local); - if (prevUpvalue == NULL) - { - // The new one is the first one in the list. - fiber->openUpvalues = createdUpvalue; - } - else - { - prevUpvalue->next = createdUpvalue; - } - - createdUpvalue->next = upvalue; - return createdUpvalue; -} - -// Closes any open upvalues that have been created for stack slots at [last] -// and above. -static void closeUpvalues(ObjFiber* fiber, Value* last) -{ - while (fiber->openUpvalues != NULL && - fiber->openUpvalues->value >= last) - { - ObjUpvalue* upvalue = fiber->openUpvalues; - - // Move the value into the upvalue itself and point the upvalue to it. - upvalue->closed = *upvalue->value; - upvalue->value = &upvalue->closed; - - // Remove it from the open upvalue list. - fiber->openUpvalues = upvalue->next; - } -} - -// Looks up a foreign method in [moduleName] on [className] with [signature]. -// -// This will try the host's foreign method binder first. If that fails, it -// falls back to handling the built-in modules. -static WrenForeignMethodFn findForeignMethod(WrenVM* vm, - const char* moduleName, - const char* className, - bool isStatic, - const char* signature) -{ - WrenForeignMethodFn method = NULL; - - if (vm->config.bindForeignMethodFn != NULL) - { - method = vm->config.bindForeignMethodFn(vm, moduleName, className, isStatic, - signature); - } - - // If the host didn't provide it, see if it's an optional one. - if (method == NULL) - { -#if WREN_OPT_META - if (strcmp(moduleName, "meta") == 0) - { - method = wrenMetaBindForeignMethod(vm, className, isStatic, signature); - } -#endif -#if WREN_OPT_RANDOM - if (strcmp(moduleName, "random") == 0) - { - method = wrenRandomBindForeignMethod(vm, className, isStatic, signature); - } -#endif - } - - return method; -} - -// Defines [methodValue] as a method on [classObj]. -// -// Handles both foreign methods where [methodValue] is a string containing the -// method's signature and Wren methods where [methodValue] is a function. -// -// Aborts the current fiber if the method is a foreign method that could not be -// found. -static void bindMethod(WrenVM* vm, int methodType, int symbol, - ObjModule* module, ObjClass* classObj, Value methodValue) -{ - const char* className = classObj->name->value; - if (methodType == CODE_METHOD_STATIC) classObj = classObj->obj.classObj; - - Method method; - if (IS_STRING(methodValue)) - { - const char* name = AS_CSTRING(methodValue); - method.type = METHOD_FOREIGN; - method.as.foreign = findForeignMethod(vm, module->name->value, - className, - methodType == CODE_METHOD_STATIC, - name); - - if (method.as.foreign == NULL) - { - vm->fiber->error = wrenStringFormat(vm, - "Could not find foreign method '@' for class $ in module '$'.", - methodValue, classObj->name->value, module->name->value); - return; - } - } - else - { - method.as.closure = AS_CLOSURE(methodValue); - method.type = METHOD_BLOCK; - - // Patch up the bytecode now that we know the superclass. - wrenBindMethodCode(classObj, method.as.closure->fn); - } - - wrenBindMethod(vm, classObj, symbol, method); -} - -static void callForeign(WrenVM* vm, ObjFiber* fiber, - WrenForeignMethodFn foreign, int numArgs) -{ - ASSERT(vm->apiStack == NULL, "Cannot already be in foreign call."); - vm->apiStack = fiber->stackTop - numArgs; - - foreign(vm); - - // Discard the stack slots for the arguments and temporaries but leave one - // for the result. - fiber->stackTop = vm->apiStack + 1; - - vm->apiStack = NULL; -} - -// Handles the current fiber having aborted because of an error. -// -// Walks the call chain of fibers, aborting each one until it hits a fiber that -// handles the error. If none do, tells the VM to stop. -static void runtimeError(WrenVM* vm) -{ - ASSERT(wrenHasError(vm->fiber), "Should only call this after an error."); - - ObjFiber* current = vm->fiber; - Value error = current->error; - - while (current != NULL) - { - // Every fiber along the call chain gets aborted with the same error. - current->error = error; - - // If the caller ran this fiber using "try", give it the error and stop. - if (current->state == FIBER_TRY) - { - // Make the caller's try method return the error message. - current->caller->stackTop[-1] = vm->fiber->error; - vm->fiber = current->caller; - return; - } - - // Otherwise, unhook the caller since we will never resume and return to it. - ObjFiber* caller = current->caller; - current->caller = NULL; - current = caller; - } - - // If we got here, nothing caught the error, so show the stack trace. - wrenDebugPrintStackTrace(vm); - vm->fiber = NULL; - vm->apiStack = NULL; -} - -// Aborts the current fiber with an appropriate method not found error for a -// method with [symbol] on [classObj]. -static void methodNotFound(WrenVM* vm, ObjClass* classObj, int symbol) -{ - vm->fiber->error = wrenStringFormat(vm, "@ does not implement '$'.", - OBJ_VAL(classObj->name), vm->methodNames.data[symbol]->value); -} - -// Looks up the previously loaded module with [name]. -// -// Returns `NULL` if no module with that name has been loaded. -static ObjModule* getModule(WrenVM* vm, Value name) -{ - Value moduleValue = wrenMapGet(vm->modules, name); - return !IS_UNDEFINED(moduleValue) ? AS_MODULE(moduleValue) : NULL; -} - -static ObjClosure* compileInModule(WrenVM* vm, Value name, const char* source, - bool isExpression, bool printErrors) -{ - // See if the module has already been loaded. - ObjModule* module = getModule(vm, name); - if (module == NULL) - { - module = wrenNewModule(vm, AS_STRING(name)); - - // It's possible for the wrenMapSet below to resize the modules map, - // and trigger a GC while doing so. When this happens it will collect - // the module we've just created. Once in the map it is safe. - wrenPushRoot(vm, (Obj*)module); - - // Store it in the VM's module registry so we don't load the same module - // multiple times. - wrenMapSet(vm, vm->modules, name, OBJ_VAL(module)); - - wrenPopRoot(vm); - - // Implicitly import the core module. - ObjModule* coreModule = getModule(vm, NULL_VAL); - for (int i = 0; i < coreModule->variables.count; i++) - { - wrenDefineVariable(vm, module, - coreModule->variableNames.data[i]->value, - coreModule->variableNames.data[i]->length, - coreModule->variables.data[i], NULL); - } - } - - ObjFn* fn = wrenCompile(vm, module, source, isExpression, printErrors); - if (fn == NULL) - { - // TODO: Should we still store the module even if it didn't compile? - return NULL; - } - - // Functions are always wrapped in closures. - wrenPushRoot(vm, (Obj*)fn); - ObjClosure* closure = wrenNewClosure(vm, fn); - wrenPopRoot(vm); // fn. - - return closure; -} - -// Verifies that [superclassValue] is a valid object to inherit from. That -// means it must be a class and cannot be the class of any built-in type. -// -// Also validates that it doesn't result in a class with too many fields and -// the other limitations foreign classes have. -// -// If successful, returns `null`. Otherwise, returns a string for the runtime -// error message. -static Value validateSuperclass(WrenVM* vm, Value name, Value superclassValue, - int numFields) -{ - // Make sure the superclass is a class. - if (!IS_CLASS(superclassValue)) - { - return wrenStringFormat(vm, - "Class '@' cannot inherit from a non-class object.", - name); - } - - // Make sure it doesn't inherit from a sealed built-in type. Primitive methods - // on these classes assume the instance is one of the other Obj___ types and - // will fail horribly if it's actually an ObjInstance. - ObjClass* superclass = AS_CLASS(superclassValue); - if (superclass == vm->classClass || - superclass == vm->fiberClass || - superclass == vm->fnClass || // Includes OBJ_CLOSURE. - superclass == vm->listClass || - superclass == vm->mapClass || - superclass == vm->rangeClass || - superclass == vm->stringClass || - superclass == vm->boolClass || - superclass == vm->nullClass || - superclass == vm->numClass) - { - return wrenStringFormat(vm, - "Class '@' cannot inherit from built-in class '@'.", - name, OBJ_VAL(superclass->name)); - } - - if (superclass->numFields == -1) - { - return wrenStringFormat(vm, - "Class '@' cannot inherit from foreign class '@'.", - name, OBJ_VAL(superclass->name)); - } - - if (numFields == -1 && superclass->numFields > 0) - { - return wrenStringFormat(vm, - "Foreign class '@' may not inherit from a class with fields.", - name); - } - - if (superclass->numFields + numFields > MAX_FIELDS) - { - return wrenStringFormat(vm, - "Class '@' may not have more than 255 fields, including inherited " - "ones.", name); - } - - return NULL_VAL; -} - -static void bindForeignClass(WrenVM* vm, ObjClass* classObj, ObjModule* module) -{ - WrenForeignClassMethods methods; - methods.allocate = NULL; - methods.finalize = NULL; - - // Check the optional built-in module first so the host can override it. - - if (vm->config.bindForeignClassFn != NULL) - { - methods = vm->config.bindForeignClassFn(vm, module->name->value, - classObj->name->value); - } - - // If the host didn't provide it, see if it's a built in optional module. - if (methods.allocate == NULL && methods.finalize == NULL) - { -#if WREN_OPT_RANDOM - if (strcmp(module->name->value, "random") == 0) - { - methods = wrenRandomBindForeignClass(vm, module->name->value, - classObj->name->value); - } -#endif - } - - Method method; - method.type = METHOD_FOREIGN; - - // Add the symbol even if there is no allocator so we can ensure that the - // symbol itself is always in the symbol table. - int symbol = wrenSymbolTableEnsure(vm, &vm->methodNames, "", 10); - if (methods.allocate != NULL) - { - method.as.foreign = methods.allocate; - wrenBindMethod(vm, classObj, symbol, method); - } - - // Add the symbol even if there is no finalizer so we can ensure that the - // symbol itself is always in the symbol table. - symbol = wrenSymbolTableEnsure(vm, &vm->methodNames, "", 10); - if (methods.finalize != NULL) - { - method.as.foreign = (WrenForeignMethodFn)methods.finalize; - wrenBindMethod(vm, classObj, symbol, method); - } -} - -// Completes the process for creating a new class. -// -// The class attributes instance and the class itself should be on the -// top of the fiber's stack. -// -// This process handles moving the attribute data for a class from -// compile time to runtime, since it now has all the attributes associated -// with a class, including for methods. -static void endClass(WrenVM* vm) -{ - // Pull the attributes and class off the stack - Value attributes = vm->fiber->stackTop[-2]; - Value classValue = vm->fiber->stackTop[-1]; - - // Remove the stack items - vm->fiber->stackTop -= 2; - - ObjClass* classObj = AS_CLASS(classValue); - classObj->attributes = attributes; -} - -// Creates a new class. -// -// If [numFields] is -1, the class is a foreign class. The name and superclass -// should be on top of the fiber's stack. After calling this, the top of the -// stack will contain the new class. -// -// Aborts the current fiber if an error occurs. -static void createClass(WrenVM* vm, int numFields, ObjModule* module) -{ - // Pull the name and superclass off the stack. - Value name = vm->fiber->stackTop[-2]; - Value superclass = vm->fiber->stackTop[-1]; - - // We have two values on the stack and we are going to leave one, so discard - // the other slot. - vm->fiber->stackTop--; - - vm->fiber->error = validateSuperclass(vm, name, superclass, numFields); - if (wrenHasError(vm->fiber)) return; - - ObjClass* classObj = wrenNewClass(vm, AS_CLASS(superclass), numFields, - AS_STRING(name)); - vm->fiber->stackTop[-1] = OBJ_VAL(classObj); - - if (numFields == -1) bindForeignClass(vm, classObj, module); -} - -static void createForeign(WrenVM* vm, ObjFiber* fiber, Value* stack) -{ - ObjClass* classObj = AS_CLASS(stack[0]); - ASSERT(classObj->numFields == -1, "Class must be a foreign class."); - - // TODO: Don't look up every time. - int symbol = wrenSymbolTableFind(&vm->methodNames, "", 10); - ASSERT(symbol != -1, "Should have defined symbol."); - - ASSERT(classObj->methods.count > symbol, "Class should have allocator."); - Method* method = &classObj->methods.data[symbol]; - ASSERT(method->type == METHOD_FOREIGN, "Allocator should be foreign."); - - // Pass the constructor arguments to the allocator as well. - ASSERT(vm->apiStack == NULL, "Cannot already be in foreign call."); - vm->apiStack = stack; - - method->as.foreign(vm); - - vm->apiStack = NULL; -} - -void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign) -{ - // TODO: Don't look up every time. - int symbol = wrenSymbolTableFind(&vm->methodNames, "", 10); - ASSERT(symbol != -1, "Should have defined symbol."); - - // If there are no finalizers, don't finalize it. - if (symbol == -1) return; - - // If the class doesn't have a finalizer, bail out. - ObjClass* classObj = foreign->obj.classObj; - if (symbol >= classObj->methods.count) return; - - Method* method = &classObj->methods.data[symbol]; - if (method->type == METHOD_NONE) return; - - ASSERT(method->type == METHOD_FOREIGN, "Finalizer should be foreign."); - - WrenFinalizerFn finalizer = (WrenFinalizerFn)method->as.foreign; - finalizer(foreign->data); -} - -// Let the host resolve an imported module name if it wants to. -static Value resolveModule(WrenVM* vm, Value name) -{ - // If the host doesn't care to resolve, leave the name alone. - if (vm->config.resolveModuleFn == NULL) return name; - - ObjFiber* fiber = vm->fiber; - ObjFn* fn = fiber->frames[fiber->numFrames - 1].closure->fn; - ObjString* importer = fn->module->name; - - const char* resolved = vm->config.resolveModuleFn(vm, importer->value, - AS_CSTRING(name)); - if (resolved == NULL) - { - vm->fiber->error = wrenStringFormat(vm, - "Could not resolve module '@' imported from '@'.", - name, OBJ_VAL(importer)); - return NULL_VAL; - } - - // If they resolved to the exact same string, we don't need to copy it. - if (resolved == AS_CSTRING(name)) return name; - - // Copy the string into a Wren String object. - name = wrenNewString(vm, resolved); - DEALLOCATE(vm, (char*)resolved); - return name; -} - -static Value importModule(WrenVM* vm, Value name) -{ - name = resolveModule(vm, name); - - // If the module is already loaded, we don't need to do anything. - Value existing = wrenMapGet(vm->modules, name); - if (!IS_UNDEFINED(existing)) return existing; - - wrenPushRoot(vm, AS_OBJ(name)); - - WrenLoadModuleResult result = {0}; - const char* source = NULL; - - // Let the host try to provide the module. - if (vm->config.loadModuleFn != NULL) - { - result = vm->config.loadModuleFn(vm, AS_CSTRING(name)); - } - - // If the host didn't provide it, see if it's a built in optional module. - if (result.source == NULL) - { - result.onComplete = NULL; - ObjString* nameString = AS_STRING(name); -#if WREN_OPT_META - if (strcmp(nameString->value, "meta") == 0) result.source = wrenMetaSource(); -#endif -#if WREN_OPT_RANDOM - if (strcmp(nameString->value, "random") == 0) result.source = wrenRandomSource(); -#endif - } - - if (result.source == NULL) - { - vm->fiber->error = wrenStringFormat(vm, "Could not load module '@'.", name); - wrenPopRoot(vm); // name. - return NULL_VAL; - } - - ObjClosure* moduleClosure = compileInModule(vm, name, result.source, false, true); - - // Now that we're done, give the result back in case there's cleanup to do. - if(result.onComplete) result.onComplete(vm, AS_CSTRING(name), result); - - if (moduleClosure == NULL) - { - vm->fiber->error = wrenStringFormat(vm, - "Could not compile module '@'.", name); - wrenPopRoot(vm); // name. - return NULL_VAL; - } - - wrenPopRoot(vm); // name. - - // Return the closure that executes the module. - return OBJ_VAL(moduleClosure); -} - -static Value getModuleVariable(WrenVM* vm, ObjModule* module, - Value variableName) -{ - ObjString* variable = AS_STRING(variableName); - uint32_t variableEntry = wrenSymbolTableFind(&module->variableNames, - variable->value, - variable->length); - - // It's a runtime error if the imported variable does not exist. - if (variableEntry != UINT32_MAX) - { - return module->variables.data[variableEntry]; - } - - vm->fiber->error = wrenStringFormat(vm, - "Could not find a variable named '@' in module '@'.", - variableName, OBJ_VAL(module->name)); - return NULL_VAL; -} - -inline static bool checkArity(WrenVM* vm, Value value, int numArgs) -{ - ASSERT(IS_CLOSURE(value), "Receiver must be a closure."); - ObjFn* fn = AS_CLOSURE(value)->fn; - - // We only care about missing arguments, not extras. The "- 1" is because - // numArgs includes the receiver, the function itself, which we don't want to - // count. - if (numArgs - 1 >= fn->arity) return true; - - vm->fiber->error = CONST_STRING(vm, "Function expects more arguments."); - return false; -} - - -// The main bytecode interpreter loop. This is where the magic happens. It is -// also, as you can imagine, highly performance critical. -static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) -{ - // Remember the current fiber so we can find it if a GC happens. - vm->fiber = fiber; - fiber->state = FIBER_ROOT; - - // Hoist these into local variables. They are accessed frequently in the loop - // but assigned less frequently. Keeping them in locals and updating them when - // a call frame has been pushed or popped gives a large speed boost. - register CallFrame* frame; - register Value* stackStart; - register uint8_t* ip; - register ObjFn* fn; - - // These macros are designed to only be invoked within this function. - #define PUSH(value) (*fiber->stackTop++ = value) - #define POP() (*(--fiber->stackTop)) - #define DROP() (fiber->stackTop--) - #define PEEK() (*(fiber->stackTop - 1)) - #define PEEK2() (*(fiber->stackTop - 2)) - #define READ_BYTE() (*ip++) - #define READ_SHORT() (ip += 2, (uint16_t)((ip[-2] << 8) | ip[-1])) - - // Use this before a CallFrame is pushed to store the local variables back - // into the current one. - #define STORE_FRAME() frame->ip = ip - - // Use this after a CallFrame has been pushed or popped to refresh the local - // variables. - #define LOAD_FRAME() \ - do \ - { \ - frame = &fiber->frames[fiber->numFrames - 1]; \ - stackStart = frame->stackStart; \ - ip = frame->ip; \ - fn = frame->closure->fn; \ - } while (false) - - // Terminates the current fiber with error string [error]. If another calling - // fiber is willing to catch the error, transfers control to it, otherwise - // exits the interpreter. - #define RUNTIME_ERROR() \ - do \ - { \ - STORE_FRAME(); \ - runtimeError(vm); \ - if (vm->fiber == NULL) return WREN_RESULT_RUNTIME_ERROR; \ - fiber = vm->fiber; \ - LOAD_FRAME(); \ - DISPATCH(); \ - } while (false) - - #if WREN_DEBUG_TRACE_INSTRUCTIONS - // Prints the stack and instruction before each instruction is executed. - #define DEBUG_TRACE_INSTRUCTIONS() \ - do \ - { \ - wrenDumpStack(fiber); \ - wrenDumpInstruction(vm, fn, (int)(ip - fn->code.data)); \ - } while (false) - #else - #define DEBUG_TRACE_INSTRUCTIONS() do { } while (false) - #endif - - #if WREN_COMPUTED_GOTO - - static void* dispatchTable[] = { - #define OPCODE(name, _) &&code_##name, -// Begin file "wren_opcodes.h" -// This defines the bytecode instructions used by the VM. It does so by invoking -// an OPCODE() macro which is expected to be defined at the point that this is -// included. (See: http://en.wikipedia.org/wiki/X_Macro for more.) -// -// The first argument is the name of the opcode. The second is its "stack -// effect" -- the amount that the op code changes the size of the stack. A -// stack effect of 1 means it pushes a value and the stack grows one larger. -// -2 means it pops two values, etc. -// -// Note that the order of instructions here affects the order of the dispatch -// table in the VM's interpreter loop. That in turn affects caching which -// affects overall performance. Take care to run benchmarks if you change the -// order here. - -// Load the constant at index [arg]. -OPCODE(CONSTANT, 1) - -// Push null onto the stack. -OPCODE(NULL, 1) - -// Push false onto the stack. -OPCODE(FALSE, 1) - -// Push true onto the stack. -OPCODE(TRUE, 1) - -// Pushes the value in the given local slot. -OPCODE(LOAD_LOCAL_0, 1) -OPCODE(LOAD_LOCAL_1, 1) -OPCODE(LOAD_LOCAL_2, 1) -OPCODE(LOAD_LOCAL_3, 1) -OPCODE(LOAD_LOCAL_4, 1) -OPCODE(LOAD_LOCAL_5, 1) -OPCODE(LOAD_LOCAL_6, 1) -OPCODE(LOAD_LOCAL_7, 1) -OPCODE(LOAD_LOCAL_8, 1) - -// Note: The compiler assumes the following _STORE instructions always -// immediately follow their corresponding _LOAD ones. - -// Pushes the value in local slot [arg]. -OPCODE(LOAD_LOCAL, 1) - -// Stores the top of stack in local slot [arg]. Does not pop it. -OPCODE(STORE_LOCAL, 0) - -// Pushes the value in upvalue [arg]. -OPCODE(LOAD_UPVALUE, 1) - -// Stores the top of stack in upvalue [arg]. Does not pop it. -OPCODE(STORE_UPVALUE, 0) - -// Pushes the value of the top-level variable in slot [arg]. -OPCODE(LOAD_MODULE_VAR, 1) - -// Stores the top of stack in top-level variable slot [arg]. Does not pop it. -OPCODE(STORE_MODULE_VAR, 0) - -// Pushes the value of the field in slot [arg] of the receiver of the current -// function. This is used for regular field accesses on "this" directly in -// methods. This instruction is faster than the more general CODE_LOAD_FIELD -// instruction. -OPCODE(LOAD_FIELD_THIS, 1) - -// Stores the top of the stack in field slot [arg] in the receiver of the -// current value. Does not pop the value. This instruction is faster than the -// more general CODE_LOAD_FIELD instruction. -OPCODE(STORE_FIELD_THIS, 0) - -// Pops an instance and pushes the value of the field in slot [arg] of it. -OPCODE(LOAD_FIELD, 0) - -// Pops an instance and stores the subsequent top of stack in field slot -// [arg] in it. Does not pop the value. -OPCODE(STORE_FIELD, -1) - -// Pop and discard the top of stack. -OPCODE(POP, -1) - -// Invoke the method with symbol [arg]. The number indicates the number of -// arguments (not including the receiver). -OPCODE(CALL_0, 0) -OPCODE(CALL_1, -1) -OPCODE(CALL_2, -2) -OPCODE(CALL_3, -3) -OPCODE(CALL_4, -4) -OPCODE(CALL_5, -5) -OPCODE(CALL_6, -6) -OPCODE(CALL_7, -7) -OPCODE(CALL_8, -8) -OPCODE(CALL_9, -9) -OPCODE(CALL_10, -10) -OPCODE(CALL_11, -11) -OPCODE(CALL_12, -12) -OPCODE(CALL_13, -13) -OPCODE(CALL_14, -14) -OPCODE(CALL_15, -15) -OPCODE(CALL_16, -16) - -// Invoke a superclass method with symbol [arg]. The number indicates the -// number of arguments (not including the receiver). -OPCODE(SUPER_0, 0) -OPCODE(SUPER_1, -1) -OPCODE(SUPER_2, -2) -OPCODE(SUPER_3, -3) -OPCODE(SUPER_4, -4) -OPCODE(SUPER_5, -5) -OPCODE(SUPER_6, -6) -OPCODE(SUPER_7, -7) -OPCODE(SUPER_8, -8) -OPCODE(SUPER_9, -9) -OPCODE(SUPER_10, -10) -OPCODE(SUPER_11, -11) -OPCODE(SUPER_12, -12) -OPCODE(SUPER_13, -13) -OPCODE(SUPER_14, -14) -OPCODE(SUPER_15, -15) -OPCODE(SUPER_16, -16) - -// Jump the instruction pointer [arg] forward. -OPCODE(JUMP, 0) - -// Jump the instruction pointer [arg] backward. -OPCODE(LOOP, 0) - -// Pop and if not truthy then jump the instruction pointer [arg] forward. -OPCODE(JUMP_IF, -1) - -// If the top of the stack is false, jump [arg] forward. Otherwise, pop and -// continue. -OPCODE(AND, -1) - -// If the top of the stack is non-false, jump [arg] forward. Otherwise, pop -// and continue. -OPCODE(OR, -1) - -// Close the upvalue for the local on the top of the stack, then pop it. -OPCODE(CLOSE_UPVALUE, -1) - -// Exit from the current function and return the value on the top of the -// stack. -OPCODE(RETURN, 0) - -// Creates a closure for the function stored at [arg] in the constant table. -// -// Following the function argument is a number of arguments, two for each -// upvalue. The first is true if the variable being captured is a local (as -// opposed to an upvalue), and the second is the index of the local or -// upvalue being captured. -// -// Pushes the created closure. -OPCODE(CLOSURE, 1) - -// Creates a new instance of a class. -// -// Assumes the class object is in slot zero, and replaces it with the new -// uninitialized instance of that class. This opcode is only emitted by the -// compiler-generated constructor metaclass methods. -OPCODE(CONSTRUCT, 0) - -// Creates a new instance of a foreign class. -// -// Assumes the class object is in slot zero, and replaces it with the new -// uninitialized instance of that class. This opcode is only emitted by the -// compiler-generated constructor metaclass methods. -OPCODE(FOREIGN_CONSTRUCT, 0) - -// Creates a class. Top of stack is the superclass. Below that is a string for -// the name of the class. Byte [arg] is the number of fields in the class. -OPCODE(CLASS, -1) - -// Ends a class. -// Atm the stack contains the class and the ClassAttributes (or null). -OPCODE(END_CLASS, -2) - -// Creates a foreign class. Top of stack is the superclass. Below that is a -// string for the name of the class. -OPCODE(FOREIGN_CLASS, -1) - -// Define a method for symbol [arg]. The class receiving the method is popped -// off the stack, then the function defining the body is popped. -// -// If a foreign method is being defined, the "function" will be a string -// identifying the foreign method. Otherwise, it will be a function or -// closure. -OPCODE(METHOD_INSTANCE, -2) - -// Define a method for symbol [arg]. The class whose metaclass will receive -// the method is popped off the stack, then the function defining the body is -// popped. -// -// If a foreign method is being defined, the "function" will be a string -// identifying the foreign method. Otherwise, it will be a function or -// closure. -OPCODE(METHOD_STATIC, -2) - -// This is executed at the end of the module's body. Pushes NULL onto the stack -// as the "return value" of the import statement and stores the module as the -// most recently imported one. -OPCODE(END_MODULE, 1) - -// Import a module whose name is the string stored at [arg] in the constant -// table. -// -// Pushes null onto the stack so that the fiber for the imported module can -// replace that with a dummy value when it returns. (Fibers always return a -// value when resuming a caller.) -OPCODE(IMPORT_MODULE, 1) - -// Import a variable from the most recently imported module. The name of the -// variable to import is at [arg] in the constant table. Pushes the loaded -// variable's value. -OPCODE(IMPORT_VARIABLE, 1) - -// This pseudo-instruction indicates the end of the bytecode. It should -// always be preceded by a `CODE_RETURN`, so is never actually executed. -OPCODE(END, 0) -// End file "wren_opcodes.h" - #undef OPCODE - }; - - #define INTERPRET_LOOP DISPATCH(); - #define CASE_CODE(name) code_##name - - #define DISPATCH() \ - do \ - { \ - DEBUG_TRACE_INSTRUCTIONS(); \ - goto *dispatchTable[instruction = (Code)READ_BYTE()]; \ - } while (false) - - #else - - #define INTERPRET_LOOP \ - loop: \ - DEBUG_TRACE_INSTRUCTIONS(); \ - switch (instruction = (Code)READ_BYTE()) - - #define CASE_CODE(name) case CODE_##name - #define DISPATCH() goto loop - - #endif - - LOAD_FRAME(); - - Code instruction; - INTERPRET_LOOP - { - CASE_CODE(LOAD_LOCAL_0): - CASE_CODE(LOAD_LOCAL_1): - CASE_CODE(LOAD_LOCAL_2): - CASE_CODE(LOAD_LOCAL_3): - CASE_CODE(LOAD_LOCAL_4): - CASE_CODE(LOAD_LOCAL_5): - CASE_CODE(LOAD_LOCAL_6): - CASE_CODE(LOAD_LOCAL_7): - CASE_CODE(LOAD_LOCAL_8): - PUSH(stackStart[instruction - CODE_LOAD_LOCAL_0]); - DISPATCH(); - - CASE_CODE(LOAD_LOCAL): - PUSH(stackStart[READ_BYTE()]); - DISPATCH(); - - CASE_CODE(LOAD_FIELD_THIS): - { - uint8_t field = READ_BYTE(); - Value receiver = stackStart[0]; - ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); - ObjInstance* instance = AS_INSTANCE(receiver); - ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); - PUSH(instance->fields[field]); - DISPATCH(); - } - - CASE_CODE(POP): DROP(); DISPATCH(); - CASE_CODE(NULL): PUSH(NULL_VAL); DISPATCH(); - CASE_CODE(FALSE): PUSH(FALSE_VAL); DISPATCH(); - CASE_CODE(TRUE): PUSH(TRUE_VAL); DISPATCH(); - - CASE_CODE(STORE_LOCAL): - stackStart[READ_BYTE()] = PEEK(); - DISPATCH(); - - CASE_CODE(CONSTANT): - PUSH(fn->constants.data[READ_SHORT()]); - DISPATCH(); - - { - // The opcodes for doing method and superclass calls share a lot of code. - // However, doing an if() test in the middle of the instruction sequence - // to handle the bit that is special to super calls makes the non-super - // call path noticeably slower. - // - // Instead, we do this old school using an explicit goto to share code for - // everything at the tail end of the call-handling code that is the same - // between normal and superclass calls. - int numArgs; - int symbol; - - Value* args; - ObjClass* classObj; - - Method* method; - - CASE_CODE(CALL_0): - CASE_CODE(CALL_1): - CASE_CODE(CALL_2): - CASE_CODE(CALL_3): - CASE_CODE(CALL_4): - CASE_CODE(CALL_5): - CASE_CODE(CALL_6): - CASE_CODE(CALL_7): - CASE_CODE(CALL_8): - CASE_CODE(CALL_9): - CASE_CODE(CALL_10): - CASE_CODE(CALL_11): - CASE_CODE(CALL_12): - CASE_CODE(CALL_13): - CASE_CODE(CALL_14): - CASE_CODE(CALL_15): - CASE_CODE(CALL_16): - // Add one for the implicit receiver argument. - numArgs = instruction - CODE_CALL_0 + 1; - symbol = READ_SHORT(); - - // The receiver is the first argument. - args = fiber->stackTop - numArgs; - classObj = wrenGetClassInline(vm, args[0]); - goto completeCall; - - CASE_CODE(SUPER_0): - CASE_CODE(SUPER_1): - CASE_CODE(SUPER_2): - CASE_CODE(SUPER_3): - CASE_CODE(SUPER_4): - CASE_CODE(SUPER_5): - CASE_CODE(SUPER_6): - CASE_CODE(SUPER_7): - CASE_CODE(SUPER_8): - CASE_CODE(SUPER_9): - CASE_CODE(SUPER_10): - CASE_CODE(SUPER_11): - CASE_CODE(SUPER_12): - CASE_CODE(SUPER_13): - CASE_CODE(SUPER_14): - CASE_CODE(SUPER_15): - CASE_CODE(SUPER_16): - // Add one for the implicit receiver argument. - numArgs = instruction - CODE_SUPER_0 + 1; - symbol = READ_SHORT(); - - // The receiver is the first argument. - args = fiber->stackTop - numArgs; - - // The superclass is stored in a constant. - classObj = AS_CLASS(fn->constants.data[READ_SHORT()]); - goto completeCall; - - completeCall: - // If the class's method table doesn't include the symbol, bail. - if (symbol >= classObj->methods.count || - (method = &classObj->methods.data[symbol])->type == METHOD_NONE) - { - methodNotFound(vm, classObj, symbol); - RUNTIME_ERROR(); - } - - switch (method->type) - { - case METHOD_PRIMITIVE: - if (method->as.primitive(vm, args)) - { - // The result is now in the first arg slot. Discard the other - // stack slots. - fiber->stackTop -= numArgs - 1; - } else { - // An error, fiber switch, or call frame change occurred. - STORE_FRAME(); - - // If we don't have a fiber to switch to, stop interpreting. - fiber = vm->fiber; - if (fiber == NULL) return WREN_RESULT_SUCCESS; - if (wrenHasError(fiber)) RUNTIME_ERROR(); - LOAD_FRAME(); - } - break; - - case METHOD_FUNCTION_CALL: - if (!checkArity(vm, args[0], numArgs)) { - RUNTIME_ERROR(); - break; - } - - STORE_FRAME(); - method->as.primitive(vm, args); - LOAD_FRAME(); - break; - - case METHOD_FOREIGN: - callForeign(vm, fiber, method->as.foreign, numArgs); - if (wrenHasError(fiber)) RUNTIME_ERROR(); - break; - - case METHOD_BLOCK: - STORE_FRAME(); - wrenCallFunction(vm, fiber, (ObjClosure*)method->as.closure, numArgs); - LOAD_FRAME(); - break; - - case METHOD_NONE: - UNREACHABLE(); - break; - } - DISPATCH(); - } - - CASE_CODE(LOAD_UPVALUE): - { - ObjUpvalue** upvalues = frame->closure->upvalues; - PUSH(*upvalues[READ_BYTE()]->value); - DISPATCH(); - } - - CASE_CODE(STORE_UPVALUE): - { - ObjUpvalue** upvalues = frame->closure->upvalues; - *upvalues[READ_BYTE()]->value = PEEK(); - DISPATCH(); - } - - CASE_CODE(LOAD_MODULE_VAR): - PUSH(fn->module->variables.data[READ_SHORT()]); - DISPATCH(); - - CASE_CODE(STORE_MODULE_VAR): - fn->module->variables.data[READ_SHORT()] = PEEK(); - DISPATCH(); - - CASE_CODE(STORE_FIELD_THIS): - { - uint8_t field = READ_BYTE(); - Value receiver = stackStart[0]; - ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); - ObjInstance* instance = AS_INSTANCE(receiver); - ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); - instance->fields[field] = PEEK(); - DISPATCH(); - } - - CASE_CODE(LOAD_FIELD): - { - uint8_t field = READ_BYTE(); - Value receiver = POP(); - ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); - ObjInstance* instance = AS_INSTANCE(receiver); - ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); - PUSH(instance->fields[field]); - DISPATCH(); - } - - CASE_CODE(STORE_FIELD): - { - uint8_t field = READ_BYTE(); - Value receiver = POP(); - ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); - ObjInstance* instance = AS_INSTANCE(receiver); - ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); - instance->fields[field] = PEEK(); - DISPATCH(); - } - - CASE_CODE(JUMP): - { - uint16_t offset = READ_SHORT(); - ip += offset; - DISPATCH(); - } - - CASE_CODE(LOOP): - { - // Jump back to the top of the loop. - uint16_t offset = READ_SHORT(); - ip -= offset; - DISPATCH(); - } - - CASE_CODE(JUMP_IF): - { - uint16_t offset = READ_SHORT(); - Value condition = POP(); - - if (wrenIsFalsyValue(condition)) ip += offset; - DISPATCH(); - } - - CASE_CODE(AND): - { - uint16_t offset = READ_SHORT(); - Value condition = PEEK(); - - if (wrenIsFalsyValue(condition)) - { - // Short-circuit the right hand side. - ip += offset; - } - else - { - // Discard the condition and evaluate the right hand side. - DROP(); - } - DISPATCH(); - } - - CASE_CODE(OR): - { - uint16_t offset = READ_SHORT(); - Value condition = PEEK(); - - if (wrenIsFalsyValue(condition)) - { - // Discard the condition and evaluate the right hand side. - DROP(); - } - else - { - // Short-circuit the right hand side. - ip += offset; - } - DISPATCH(); - } - - CASE_CODE(CLOSE_UPVALUE): - // Close the upvalue for the local if we have one. - closeUpvalues(fiber, fiber->stackTop - 1); - DROP(); - DISPATCH(); - - CASE_CODE(RETURN): - { - Value result = POP(); - fiber->numFrames--; - - // Close any upvalues still in scope. - closeUpvalues(fiber, stackStart); - - // If the fiber is complete, end it. - if (fiber->numFrames == 0) - { - // See if there's another fiber to return to. If not, we're done. - if (fiber->caller == NULL) - { - // Store the final result value at the beginning of the stack so the - // C API can get it. - fiber->stack[0] = result; - fiber->stackTop = fiber->stack + 1; - return WREN_RESULT_SUCCESS; - } - - ObjFiber* resumingFiber = fiber->caller; - fiber->caller = NULL; - fiber = resumingFiber; - vm->fiber = resumingFiber; - - // Store the result in the resuming fiber. - fiber->stackTop[-1] = result; - } - else - { - // Store the result of the block in the first slot, which is where the - // caller expects it. - stackStart[0] = result; - - // Discard the stack slots for the call frame (leaving one slot for the - // result). - fiber->stackTop = frame->stackStart + 1; - } - - LOAD_FRAME(); - DISPATCH(); - } - - CASE_CODE(CONSTRUCT): - ASSERT(IS_CLASS(stackStart[0]), "'this' should be a class."); - stackStart[0] = wrenNewInstance(vm, AS_CLASS(stackStart[0])); - DISPATCH(); - - CASE_CODE(FOREIGN_CONSTRUCT): - ASSERT(IS_CLASS(stackStart[0]), "'this' should be a class."); - createForeign(vm, fiber, stackStart); - if (wrenHasError(fiber)) RUNTIME_ERROR(); - DISPATCH(); - - CASE_CODE(CLOSURE): - { - // Create the closure and push it on the stack before creating upvalues - // so that it doesn't get collected. - ObjFn* function = AS_FN(fn->constants.data[READ_SHORT()]); - ObjClosure* closure = wrenNewClosure(vm, function); - PUSH(OBJ_VAL(closure)); - - // Capture upvalues, if any. - for (int i = 0; i < function->numUpvalues; i++) - { - uint8_t isLocal = READ_BYTE(); - uint8_t index = READ_BYTE(); - if (isLocal) - { - // Make an new upvalue to close over the parent's local variable. - closure->upvalues[i] = captureUpvalue(vm, fiber, - frame->stackStart + index); - } - else - { - // Use the same upvalue as the current call frame. - closure->upvalues[i] = frame->closure->upvalues[index]; - } - } - DISPATCH(); - } - - CASE_CODE(END_CLASS): - { - endClass(vm); - if (wrenHasError(fiber)) RUNTIME_ERROR(); - DISPATCH(); - } - - CASE_CODE(CLASS): - { - createClass(vm, READ_BYTE(), NULL); - if (wrenHasError(fiber)) RUNTIME_ERROR(); - DISPATCH(); - } - - CASE_CODE(FOREIGN_CLASS): - { - createClass(vm, -1, fn->module); - if (wrenHasError(fiber)) RUNTIME_ERROR(); - DISPATCH(); - } - - CASE_CODE(METHOD_INSTANCE): - CASE_CODE(METHOD_STATIC): - { - uint16_t symbol = READ_SHORT(); - ObjClass* classObj = AS_CLASS(PEEK()); - Value method = PEEK2(); - bindMethod(vm, instruction, symbol, fn->module, classObj, method); - if (wrenHasError(fiber)) RUNTIME_ERROR(); - DROP(); - DROP(); - DISPATCH(); - } - - CASE_CODE(END_MODULE): - { - vm->lastModule = fn->module; - PUSH(NULL_VAL); - DISPATCH(); - } - - CASE_CODE(IMPORT_MODULE): - { - // Make a slot on the stack for the module's fiber to place the return - // value. It will be popped after this fiber is resumed. Store the - // imported module's closure in the slot in case a GC happens when - // invoking the closure. - PUSH(importModule(vm, fn->constants.data[READ_SHORT()])); - if (wrenHasError(fiber)) RUNTIME_ERROR(); - - // If we get a closure, call it to execute the module body. - if (IS_CLOSURE(PEEK())) - { - STORE_FRAME(); - ObjClosure* closure = AS_CLOSURE(PEEK()); - wrenCallFunction(vm, fiber, closure, 1); - LOAD_FRAME(); - } - else - { - // The module has already been loaded. Remember it so we can import - // variables from it if needed. - vm->lastModule = AS_MODULE(PEEK()); - } - - DISPATCH(); - } - - CASE_CODE(IMPORT_VARIABLE): - { - Value variable = fn->constants.data[READ_SHORT()]; - ASSERT(vm->lastModule != NULL, "Should have already imported module."); - Value result = getModuleVariable(vm, vm->lastModule, variable); - if (wrenHasError(fiber)) RUNTIME_ERROR(); - - PUSH(result); - DISPATCH(); - } - - CASE_CODE(END): - // A CODE_END should always be preceded by a CODE_RETURN. If we get here, - // the compiler generated wrong code. - UNREACHABLE(); - } - - // We should only exit this function from an explicit return from CODE_RETURN - // or a runtime error. - UNREACHABLE(); - return WREN_RESULT_RUNTIME_ERROR; - - #undef READ_BYTE - #undef READ_SHORT -} - -WrenHandle* wrenMakeCallHandle(WrenVM* vm, const char* signature) -{ - ASSERT(signature != NULL, "Signature cannot be NULL."); - - int signatureLength = (int)strlen(signature); - ASSERT(signatureLength > 0, "Signature cannot be empty."); - - // Count the number parameters the method expects. - int numParams = 0; - if (signature[signatureLength - 1] == ')') - { - for (int i = signatureLength - 1; i > 0 && signature[i] != '('; i--) - { - if (signature[i] == '_') numParams++; - } - } - - // Count subscript arguments. - if (signature[0] == '[') - { - for (int i = 0; i < signatureLength && signature[i] != ']'; i++) - { - if (signature[i] == '_') numParams++; - } - } - - // Add the signatue to the method table. - int method = wrenSymbolTableEnsure(vm, &vm->methodNames, - signature, signatureLength); - - // Create a little stub function that assumes the arguments are on the stack - // and calls the method. - ObjFn* fn = wrenNewFunction(vm, NULL, numParams + 1); - - // Wrap the function in a closure and then in a handle. Do this here so it - // doesn't get collected as we fill it in. - WrenHandle* value = wrenMakeHandle(vm, OBJ_VAL(fn)); - value->value = OBJ_VAL(wrenNewClosure(vm, fn)); - - wrenByteBufferWrite(vm, &fn->code, (uint8_t)(CODE_CALL_0 + numParams)); - wrenByteBufferWrite(vm, &fn->code, (method >> 8) & 0xff); - wrenByteBufferWrite(vm, &fn->code, method & 0xff); - wrenByteBufferWrite(vm, &fn->code, CODE_RETURN); - wrenByteBufferWrite(vm, &fn->code, CODE_END); - wrenIntBufferFill(vm, &fn->debug->sourceLines, 0, 5); - wrenFunctionBindName(vm, fn, signature, signatureLength); - - return value; -} - -WrenInterpretResult wrenCall(WrenVM* vm, WrenHandle* method) -{ - ASSERT(method != NULL, "Method cannot be NULL."); - ASSERT(IS_CLOSURE(method->value), "Method must be a method handle."); - ASSERT(vm->fiber != NULL, "Must set up arguments for call first."); - ASSERT(vm->apiStack != NULL, "Must set up arguments for call first."); - ASSERT(vm->fiber->numFrames == 0, "Can not call from a foreign method."); - - ObjClosure* closure = AS_CLOSURE(method->value); - - ASSERT(vm->fiber->stackTop - vm->fiber->stack >= closure->fn->arity, - "Stack must have enough arguments for method."); - - // Clear the API stack. Now that wrenCall() has control, we no longer need - // it. We use this being non-null to tell if re-entrant calls to foreign - // methods are happening, so it's important to clear it out now so that you - // can call foreign methods from within calls to wrenCall(). - vm->apiStack = NULL; - - // Discard any extra temporary slots. We take for granted that the stub - // function has exactly one slot for each argument. - vm->fiber->stackTop = &vm->fiber->stack[closure->fn->maxSlots]; - - wrenCallFunction(vm, vm->fiber, closure, 0); - WrenInterpretResult result = runInterpreter(vm, vm->fiber); - - // If the call didn't abort, then set up the API stack to point to the - // beginning of the stack so the host can access the call's return value. - if (vm->fiber != NULL) vm->apiStack = vm->fiber->stack; - - return result; -} - -WrenHandle* wrenMakeHandle(WrenVM* vm, Value value) -{ - if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value)); - - // Make a handle for it. - WrenHandle* handle = ALLOCATE(vm, WrenHandle); - handle->value = value; - - if (IS_OBJ(value)) wrenPopRoot(vm); - - // Add it to the front of the linked list of handles. - if (vm->handles != NULL) vm->handles->prev = handle; - handle->prev = NULL; - handle->next = vm->handles; - vm->handles = handle; - - return handle; -} - -void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle) -{ - ASSERT(handle != NULL, "Handle cannot be NULL."); - - // Update the VM's head pointer if we're releasing the first handle. - if (vm->handles == handle) vm->handles = handle->next; - - // Unlink it from the list. - if (handle->prev != NULL) handle->prev->next = handle->next; - if (handle->next != NULL) handle->next->prev = handle->prev; - - // Clear it out. This isn't strictly necessary since we're going to free it, - // but it makes for easier debugging. - handle->prev = NULL; - handle->next = NULL; - handle->value = NULL_VAL; - DEALLOCATE(vm, handle); -} - -WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module, - const char* source) -{ - ObjClosure* closure = wrenCompileSource(vm, module, source, false, true); - if (closure == NULL) return WREN_RESULT_COMPILE_ERROR; - - wrenPushRoot(vm, (Obj*)closure); - ObjFiber* fiber = wrenNewFiber(vm, closure); - wrenPopRoot(vm); // closure. - vm->apiStack = NULL; - - return runInterpreter(vm, fiber); -} - -ObjClosure* wrenCompileSource(WrenVM* vm, const char* module, const char* source, - bool isExpression, bool printErrors) -{ - Value nameValue = NULL_VAL; - if (module != NULL) - { - nameValue = wrenNewString(vm, module); - wrenPushRoot(vm, AS_OBJ(nameValue)); - } - - ObjClosure* closure = compileInModule(vm, nameValue, source, - isExpression, printErrors); - - if (module != NULL) wrenPopRoot(vm); // nameValue. - return closure; -} - -Value wrenGetModuleVariable(WrenVM* vm, Value moduleName, Value variableName) -{ - ObjModule* module = getModule(vm, moduleName); - if (module == NULL) - { - vm->fiber->error = wrenStringFormat(vm, "Module '@' is not loaded.", - moduleName); - return NULL_VAL; - } - - return getModuleVariable(vm, module, variableName); -} - -Value wrenFindVariable(WrenVM* vm, ObjModule* module, const char* name) -{ - int symbol = wrenSymbolTableFind(&module->variableNames, name, strlen(name)); - return module->variables.data[symbol]; -} - -int wrenDeclareVariable(WrenVM* vm, ObjModule* module, const char* name, - size_t length, int line) -{ - if (module->variables.count == MAX_MODULE_VARS) return -2; - - // Implicitly defined variables get a "value" that is the line where the - // variable is first used. We'll use that later to report an error on the - // right line. - wrenValueBufferWrite(vm, &module->variables, NUM_VAL(line)); - return wrenSymbolTableAdd(vm, &module->variableNames, name, length); -} - -int wrenDefineVariable(WrenVM* vm, ObjModule* module, const char* name, - size_t length, Value value, int* line) -{ - if (module->variables.count == MAX_MODULE_VARS) return -2; - - if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value)); - - // See if the variable is already explicitly or implicitly declared. - int symbol = wrenSymbolTableFind(&module->variableNames, name, length); - - if (symbol == -1) - { - // Brand new variable. - symbol = wrenSymbolTableAdd(vm, &module->variableNames, name, length); - wrenValueBufferWrite(vm, &module->variables, value); - } - else if (IS_NUM(module->variables.data[symbol])) - { - // An implicitly declared variable's value will always be a number. - // Now we have a real definition. - if(line) *line = (int)AS_NUM(module->variables.data[symbol]); - module->variables.data[symbol] = value; - - // If this was a localname we want to error if it was - // referenced before this definition. - if (wrenIsLocalName(name)) symbol = -3; - } - else - { - // Already explicitly declared. - symbol = -1; - } - - if (IS_OBJ(value)) wrenPopRoot(vm); - - return symbol; -} - -// TODO: Inline? -void wrenPushRoot(WrenVM* vm, Obj* obj) -{ - ASSERT(obj != NULL, "Can't root NULL."); - ASSERT(vm->numTempRoots < WREN_MAX_TEMP_ROOTS, "Too many temporary roots."); - - vm->tempRoots[vm->numTempRoots++] = obj; -} - -void wrenPopRoot(WrenVM* vm) -{ - ASSERT(vm->numTempRoots > 0, "No temporary roots to release."); - vm->numTempRoots--; -} - -int wrenGetSlotCount(WrenVM* vm) -{ - if (vm->apiStack == NULL) return 0; - - return (int)(vm->fiber->stackTop - vm->apiStack); -} - -void wrenEnsureSlots(WrenVM* vm, int numSlots) -{ - // If we don't have a fiber accessible, create one for the API to use. - if (vm->apiStack == NULL) - { - vm->fiber = wrenNewFiber(vm, NULL); - vm->apiStack = vm->fiber->stack; - } - - int currentSize = (int)(vm->fiber->stackTop - vm->apiStack); - if (currentSize >= numSlots) return; - - // Grow the stack if needed. - int needed = (int)(vm->apiStack - vm->fiber->stack) + numSlots; - wrenEnsureStack(vm, vm->fiber, needed); - - vm->fiber->stackTop = vm->apiStack + numSlots; -} - -// Ensures that [slot] is a valid index into the API's stack of slots. -static void validateApiSlot(WrenVM* vm, int slot) -{ - ASSERT(slot >= 0, "Slot cannot be negative."); - ASSERT(slot < wrenGetSlotCount(vm), "Not that many slots."); -} - -// Gets the type of the object in [slot]. -WrenType wrenGetSlotType(WrenVM* vm, int slot) -{ - validateApiSlot(vm, slot); - if (IS_BOOL(vm->apiStack[slot])) return WREN_TYPE_BOOL; - if (IS_NUM(vm->apiStack[slot])) return WREN_TYPE_NUM; - if (IS_FOREIGN(vm->apiStack[slot])) return WREN_TYPE_FOREIGN; - if (IS_LIST(vm->apiStack[slot])) return WREN_TYPE_LIST; - if (IS_MAP(vm->apiStack[slot])) return WREN_TYPE_MAP; - if (IS_NULL(vm->apiStack[slot])) return WREN_TYPE_NULL; - if (IS_STRING(vm->apiStack[slot])) return WREN_TYPE_STRING; - - return WREN_TYPE_UNKNOWN; -} - -bool wrenGetSlotBool(WrenVM* vm, int slot) -{ - validateApiSlot(vm, slot); - ASSERT(IS_BOOL(vm->apiStack[slot]), "Slot must hold a bool."); - - return AS_BOOL(vm->apiStack[slot]); -} - -const char* wrenGetSlotBytes(WrenVM* vm, int slot, int* length) -{ - validateApiSlot(vm, slot); - ASSERT(IS_STRING(vm->apiStack[slot]), "Slot must hold a string."); - - ObjString* string = AS_STRING(vm->apiStack[slot]); - *length = string->length; - return string->value; -} - -double wrenGetSlotDouble(WrenVM* vm, int slot) -{ - validateApiSlot(vm, slot); - ASSERT(IS_NUM(vm->apiStack[slot]), "Slot must hold a number."); - - return AS_NUM(vm->apiStack[slot]); -} - -void* wrenGetSlotForeign(WrenVM* vm, int slot) -{ - validateApiSlot(vm, slot); - ASSERT(IS_FOREIGN(vm->apiStack[slot]), - "Slot must hold a foreign instance."); - - return AS_FOREIGN(vm->apiStack[slot])->data; -} - -const char* wrenGetSlotString(WrenVM* vm, int slot) -{ - validateApiSlot(vm, slot); - ASSERT(IS_STRING(vm->apiStack[slot]), "Slot must hold a string."); - - return AS_CSTRING(vm->apiStack[slot]); -} - -WrenHandle* wrenGetSlotHandle(WrenVM* vm, int slot) -{ - validateApiSlot(vm, slot); - return wrenMakeHandle(vm, vm->apiStack[slot]); -} - -// Stores [value] in [slot] in the foreign call stack. -static void setSlot(WrenVM* vm, int slot, Value value) -{ - validateApiSlot(vm, slot); - vm->apiStack[slot] = value; -} - -void wrenSetSlotBool(WrenVM* vm, int slot, bool value) -{ - setSlot(vm, slot, BOOL_VAL(value)); -} - -void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length) -{ - ASSERT(bytes != NULL, "Byte array cannot be NULL."); - setSlot(vm, slot, wrenNewStringLength(vm, bytes, length)); -} - -void wrenSetSlotDouble(WrenVM* vm, int slot, double value) -{ - setSlot(vm, slot, NUM_VAL(value)); -} - -void* wrenSetSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t size) -{ - validateApiSlot(vm, slot); - validateApiSlot(vm, classSlot); - ASSERT(IS_CLASS(vm->apiStack[classSlot]), "Slot must hold a class."); - - ObjClass* classObj = AS_CLASS(vm->apiStack[classSlot]); - ASSERT(classObj->numFields == -1, "Class must be a foreign class."); - - ObjForeign* foreign = wrenNewForeign(vm, classObj, size); - vm->apiStack[slot] = OBJ_VAL(foreign); - - return (void*)foreign->data; -} - -void wrenSetSlotNewList(WrenVM* vm, int slot) -{ - setSlot(vm, slot, OBJ_VAL(wrenNewList(vm, 0))); -} - -void wrenSetSlotNewMap(WrenVM* vm, int slot) -{ - setSlot(vm, slot, OBJ_VAL(wrenNewMap(vm))); -} - -void wrenSetSlotNull(WrenVM* vm, int slot) -{ - setSlot(vm, slot, NULL_VAL); -} - -void wrenSetSlotString(WrenVM* vm, int slot, const char* text) -{ - ASSERT(text != NULL, "String cannot be NULL."); - - setSlot(vm, slot, wrenNewString(vm, text)); -} - -void wrenSetSlotHandle(WrenVM* vm, int slot, WrenHandle* handle) -{ - ASSERT(handle != NULL, "Handle cannot be NULL."); - - setSlot(vm, slot, handle->value); -} - -int wrenGetListCount(WrenVM* vm, int slot) -{ - validateApiSlot(vm, slot); - ASSERT(IS_LIST(vm->apiStack[slot]), "Slot must hold a list."); - - ValueBuffer elements = AS_LIST(vm->apiStack[slot])->elements; - return elements.count; -} - -void wrenGetListElement(WrenVM* vm, int listSlot, int index, int elementSlot) -{ - validateApiSlot(vm, listSlot); - validateApiSlot(vm, elementSlot); - ASSERT(IS_LIST(vm->apiStack[listSlot]), "Slot must hold a list."); - - ValueBuffer elements = AS_LIST(vm->apiStack[listSlot])->elements; - - uint32_t usedIndex = wrenValidateIndex(elements.count, index); - ASSERT(usedIndex != UINT32_MAX, "Index out of bounds."); - - vm->apiStack[elementSlot] = elements.data[usedIndex]; -} - -void wrenSetListElement(WrenVM* vm, int listSlot, int index, int elementSlot) -{ - validateApiSlot(vm, listSlot); - validateApiSlot(vm, elementSlot); - ASSERT(IS_LIST(vm->apiStack[listSlot]), "Slot must hold a list."); - - ObjList* list = AS_LIST(vm->apiStack[listSlot]); - - uint32_t usedIndex = wrenValidateIndex(list->elements.count, index); - ASSERT(usedIndex != UINT32_MAX, "Index out of bounds."); - - list->elements.data[usedIndex] = vm->apiStack[elementSlot]; -} - -void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot) -{ - validateApiSlot(vm, listSlot); - validateApiSlot(vm, elementSlot); - ASSERT(IS_LIST(vm->apiStack[listSlot]), "Must insert into a list."); - - ObjList* list = AS_LIST(vm->apiStack[listSlot]); - - // Negative indices count from the end. - // We don't use wrenValidateIndex here because insert allows 1 past the end. - if (index < 0) index = list->elements.count + 1 + index; - - ASSERT(index <= list->elements.count, "Index out of bounds."); - - wrenListInsert(vm, list, vm->apiStack[elementSlot], index); -} - -int wrenGetMapCount(WrenVM* vm, int slot) -{ - validateApiSlot(vm, slot); - ASSERT(IS_MAP(vm->apiStack[slot]), "Slot must hold a map."); - - ObjMap* map = AS_MAP(vm->apiStack[slot]); - return map->count; -} - -bool wrenGetMapContainsKey(WrenVM* vm, int mapSlot, int keySlot) -{ - validateApiSlot(vm, mapSlot); - validateApiSlot(vm, keySlot); - ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Slot must hold a map."); - - Value key = vm->apiStack[keySlot]; - ASSERT(wrenMapIsValidKey(key), "Key must be a value type"); - if (!validateKey(vm, key)) return false; - - ObjMap* map = AS_MAP(vm->apiStack[mapSlot]); - Value value = wrenMapGet(map, key); - - return !IS_UNDEFINED(value); -} - -void wrenGetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot) -{ - validateApiSlot(vm, mapSlot); - validateApiSlot(vm, keySlot); - validateApiSlot(vm, valueSlot); - ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Slot must hold a map."); - - ObjMap* map = AS_MAP(vm->apiStack[mapSlot]); - Value value = wrenMapGet(map, vm->apiStack[keySlot]); - if (IS_UNDEFINED(value)) { - value = NULL_VAL; - } - - vm->apiStack[valueSlot] = value; -} - -void wrenSetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot) -{ - validateApiSlot(vm, mapSlot); - validateApiSlot(vm, keySlot); - validateApiSlot(vm, valueSlot); - ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Must insert into a map."); - - Value key = vm->apiStack[keySlot]; - ASSERT(wrenMapIsValidKey(key), "Key must be a value type"); - - if (!validateKey(vm, key)) { - return; - } - - Value value = vm->apiStack[valueSlot]; - ObjMap* map = AS_MAP(vm->apiStack[mapSlot]); - - wrenMapSet(vm, map, key, value); -} - -void wrenRemoveMapValue(WrenVM* vm, int mapSlot, int keySlot, - int removedValueSlot) -{ - validateApiSlot(vm, mapSlot); - validateApiSlot(vm, keySlot); - ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Slot must hold a map."); - - Value key = vm->apiStack[keySlot]; - if (!validateKey(vm, key)) { - return; - } - - ObjMap* map = AS_MAP(vm->apiStack[mapSlot]); - Value removed = wrenMapRemoveKey(vm, map, key); - setSlot(vm, removedValueSlot, removed); -} - -void wrenGetVariable(WrenVM* vm, const char* module, const char* name, - int slot) -{ - ASSERT(module != NULL, "Module cannot be NULL."); - ASSERT(name != NULL, "Variable name cannot be NULL."); - - Value moduleName = wrenStringFormat(vm, "$", module); - wrenPushRoot(vm, AS_OBJ(moduleName)); - - ObjModule* moduleObj = getModule(vm, moduleName); - ASSERT(moduleObj != NULL, "Could not find module."); - - wrenPopRoot(vm); // moduleName. - - int variableSlot = wrenSymbolTableFind(&moduleObj->variableNames, - name, strlen(name)); - ASSERT(variableSlot != -1, "Could not find variable."); - - setSlot(vm, slot, moduleObj->variables.data[variableSlot]); -} - -bool wrenHasVariable(WrenVM* vm, const char* module, const char* name) -{ - ASSERT(module != NULL, "Module cannot be NULL."); - ASSERT(name != NULL, "Variable name cannot be NULL."); - - Value moduleName = wrenStringFormat(vm, "$", module); - wrenPushRoot(vm, AS_OBJ(moduleName)); - - //We don't use wrenHasModule since we want to use the module object. - ObjModule* moduleObj = getModule(vm, moduleName); - ASSERT(moduleObj != NULL, "Could not find module."); - - wrenPopRoot(vm); // moduleName. - - int variableSlot = wrenSymbolTableFind(&moduleObj->variableNames, - name, strlen(name)); - - return variableSlot != -1; -} - -bool wrenHasModule(WrenVM* vm, const char* module) -{ - ASSERT(module != NULL, "Module cannot be NULL."); - - Value moduleName = wrenStringFormat(vm, "$", module); - wrenPushRoot(vm, AS_OBJ(moduleName)); - - ObjModule* moduleObj = getModule(vm, moduleName); - - wrenPopRoot(vm); // moduleName. - - return moduleObj != NULL; -} - -void wrenAbortFiber(WrenVM* vm, int slot) -{ - validateApiSlot(vm, slot); - vm->fiber->error = vm->apiStack[slot]; -} - -void* wrenGetUserData(WrenVM* vm) -{ - return vm->config.userData; -} - -void wrenSetUserData(WrenVM* vm, void* userData) -{ - vm->config.userData = userData; -} -// End file "wren_vm.c" -// Begin file "wren_opt_random.c" - -#if WREN_OPT_RANDOM - -#include -#include - - -// Begin file "wren_opt_random.wren.inc" -// Generated automatically from src/optional/wren_opt_random.wren. Do not edit. -static const char* randomModuleSource = -"foreign class Random {\n" -" construct new() {\n" -" seed_()\n" -" }\n" -"\n" -" construct new(seed) {\n" -" if (seed is Num) {\n" -" seed_(seed)\n" -" } else if (seed is Sequence) {\n" -" if (seed.isEmpty) Fiber.abort(\"Sequence cannot be empty.\")\n" -"\n" -" // TODO: Empty sequence.\n" -" var seeds = []\n" -" for (element in seed) {\n" -" if (!(element is Num)) Fiber.abort(\"Sequence elements must all be numbers.\")\n" -"\n" -" seeds.add(element)\n" -" if (seeds.count == 16) break\n" -" }\n" -"\n" -" // Cycle the values to fill in any missing slots.\n" -" var i = 0\n" -" while (seeds.count < 16) {\n" -" seeds.add(seeds[i])\n" -" i = i + 1\n" -" }\n" -"\n" -" seed_(\n" -" seeds[0], seeds[1], seeds[2], seeds[3],\n" -" seeds[4], seeds[5], seeds[6], seeds[7],\n" -" seeds[8], seeds[9], seeds[10], seeds[11],\n" -" seeds[12], seeds[13], seeds[14], seeds[15])\n" -" } else {\n" -" Fiber.abort(\"Seed must be a number or a sequence of numbers.\")\n" -" }\n" -" }\n" -"\n" -" foreign seed_()\n" -" foreign seed_(seed)\n" -" foreign seed_(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15, n16)\n" -"\n" -" foreign float()\n" -" float(end) { float() * end }\n" -" float(start, end) { float() * (end - start) + start }\n" -"\n" -" foreign int()\n" -" int(end) { (float() * end).floor }\n" -" int(start, end) { (float() * (end - start)).floor + start }\n" -"\n" -" sample(list) {\n" -" if (list.count == 0) Fiber.abort(\"Not enough elements to sample.\")\n" -" return list[int(list.count)]\n" -" }\n" -" sample(list, count) {\n" -" if (count > list.count) Fiber.abort(\"Not enough elements to sample.\")\n" -"\n" -" var result = []\n" -"\n" -" // The algorithm described in \"Programming pearls: a sample of brilliance\".\n" -" // Use a hash map for sample sizes less than 1/4 of the population size and\n" -" // an array of booleans for larger samples. This simple heuristic improves\n" -" // performance for large sample sizes as well as reduces memory usage.\n" -" if (count * 4 < list.count) {\n" -" var picked = {}\n" -" for (i in list.count - count...list.count) {\n" -" var index = int(i + 1)\n" -" if (picked.containsKey(index)) index = i\n" -" picked[index] = true\n" -" result.add(list[index])\n" -" }\n" -" } else {\n" -" var picked = List.filled(list.count, false)\n" -" for (i in list.count - count...list.count) {\n" -" var index = int(i + 1)\n" -" if (picked[index]) index = i\n" -" picked[index] = true\n" -" result.add(list[index])\n" -" }\n" -" }\n" -"\n" -" return result\n" -" }\n" -"\n" -" shuffle(list) {\n" -" if (list.isEmpty) return\n" -"\n" -" // Fisher-Yates shuffle.\n" -" for (i in 0...list.count - 1) {\n" -" var from = int(i, list.count)\n" -" var temp = list[from]\n" -" list[from] = list[i]\n" -" list[i] = temp\n" -" }\n" -" }\n" -"}\n"; -// End file "wren_opt_random.wren.inc" - -// Implements the well equidistributed long-period linear PRNG (WELL512a). -// -// https://en.wikipedia.org/wiki/Well_equidistributed_long-period_linear -typedef struct -{ - uint32_t state[16]; - uint32_t index; -} Well512; - -// Code from: http://www.lomont.org/Math/Papers/2008/Lomont_PRNG_2008.pdf -static uint32_t advanceState(Well512* well) -{ - uint32_t a, b, c, d; - a = well->state[well->index]; - c = well->state[(well->index + 13) & 15]; - b = a ^ c ^ (a << 16) ^ (c << 15); - c = well->state[(well->index + 9) & 15]; - c ^= (c >> 11); - a = well->state[well->index] = b ^ c; - d = a ^ ((a << 5) & 0xda442d24U); - - well->index = (well->index + 15) & 15; - a = well->state[well->index]; - well->state[well->index] = a ^ b ^ d ^ (a << 2) ^ (b << 18) ^ (c << 28); - return well->state[well->index]; -} - -static void randomAllocate(WrenVM* vm) -{ - Well512* well = (Well512*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(Well512)); - well->index = 0; -} - -static void randomSeed0(WrenVM* vm) -{ - Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); - - srand((uint32_t)time(NULL)); - for (int i = 0; i < 16; i++) - { - well->state[i] = rand(); - } -} - -static void randomSeed1(WrenVM* vm) -{ - Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); - - srand((uint32_t)wrenGetSlotDouble(vm, 1)); - for (int i = 0; i < 16; i++) - { - well->state[i] = rand(); - } -} - -static void randomSeed16(WrenVM* vm) -{ - Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); - - for (int i = 0; i < 16; i++) - { - well->state[i] = (uint32_t)wrenGetSlotDouble(vm, i + 1); - } -} - -static void randomFloat(WrenVM* vm) -{ - Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); - - // A double has 53 bits of precision in its mantissa, and we'd like to take - // full advantage of that, so we need 53 bits of random source data. - - // First, start with 32 random bits, shifted to the left 21 bits. - double result = (double)advanceState(well) * (1 << 21); - - // Then add another 21 random bits. - result += (double)(advanceState(well) & ((1 << 21) - 1)); - - // Now we have a number from 0 - (2^53). Divide be the range to get a double - // from 0 to 1.0 (half-inclusive). - result /= 9007199254740992.0; - - wrenSetSlotDouble(vm, 0, result); -} - -static void randomInt0(WrenVM* vm) -{ - Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); - - wrenSetSlotDouble(vm, 0, (double)advanceState(well)); -} - -const char* wrenRandomSource() -{ - return randomModuleSource; -} - -WrenForeignClassMethods wrenRandomBindForeignClass(WrenVM* vm, - const char* module, - const char* className) -{ - ASSERT(strcmp(className, "Random") == 0, "Should be in Random class."); - WrenForeignClassMethods methods; - methods.allocate = randomAllocate; - methods.finalize = NULL; - return methods; -} - -WrenForeignMethodFn wrenRandomBindForeignMethod(WrenVM* vm, - const char* className, - bool isStatic, - const char* signature) -{ - ASSERT(strcmp(className, "Random") == 0, "Should be in Random class."); - - if (strcmp(signature, "") == 0) return randomAllocate; - if (strcmp(signature, "seed_()") == 0) return randomSeed0; - if (strcmp(signature, "seed_(_)") == 0) return randomSeed1; - - if (strcmp(signature, "seed_(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)") == 0) - { - return randomSeed16; - } - - if (strcmp(signature, "float()") == 0) return randomFloat; - if (strcmp(signature, "int()") == 0) return randomInt0; - - ASSERT(false, "Unknown method."); - return NULL; -} - -#endif -// End file "wren_opt_random.c" -// Begin file "wren_opt_meta.c" - -#if WREN_OPT_META - -#include - -// Begin file "wren_opt_meta.wren.inc" -// Generated automatically from src/optional/wren_opt_meta.wren. Do not edit. -static const char* metaModuleSource = -"class Meta {\n" -" static getModuleVariables(module) {\n" -" if (!(module is String)) Fiber.abort(\"Module name must be a string.\")\n" -" var result = getModuleVariables_(module)\n" -" if (result != null) return result\n" -"\n" -" Fiber.abort(\"Could not find a module named '%(module)'.\")\n" -" }\n" -"\n" -" static eval(source) {\n" -" if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n" -"\n" -" var closure = compile_(source, false, false)\n" -" // TODO: Include compile errors.\n" -" if (closure == null) Fiber.abort(\"Could not compile source code.\")\n" -"\n" -" closure.call()\n" -" }\n" -"\n" -" static compileExpression(source) {\n" -" if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n" -" return compile_(source, true, true)\n" -" }\n" -"\n" -" static compile(source) {\n" -" if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n" -" return compile_(source, false, true)\n" -" }\n" -"\n" -" foreign static compile_(source, isExpression, printErrors)\n" -" foreign static getModuleVariables_(module)\n" -"}\n"; -// End file "wren_opt_meta.wren.inc" - -void metaCompile(WrenVM* vm) -{ - const char* source = wrenGetSlotString(vm, 1); - bool isExpression = wrenGetSlotBool(vm, 2); - bool printErrors = wrenGetSlotBool(vm, 3); - - // TODO: Allow passing in module? - // Look up the module surrounding the callsite. This is brittle. The -2 walks - // up the callstack assuming that the meta module has one level of - // indirection before hitting the user's code. Any change to meta may require - // this constant to be tweaked. - ObjFiber* currentFiber = vm->fiber; - ObjFn* fn = currentFiber->frames[currentFiber->numFrames - 2].closure->fn; - ObjString* module = fn->module->name; - - ObjClosure* closure = wrenCompileSource(vm, module->value, source, - isExpression, printErrors); - - // Return the result. We can't use the public API for this since we have a - // bare ObjClosure*. - if (closure == NULL) - { - vm->apiStack[0] = NULL_VAL; - } - else - { - vm->apiStack[0] = OBJ_VAL(closure); - } -} - -void metaGetModuleVariables(WrenVM* vm) -{ - wrenEnsureSlots(vm, 3); - - Value moduleValue = wrenMapGet(vm->modules, vm->apiStack[1]); - if (IS_UNDEFINED(moduleValue)) - { - vm->apiStack[0] = NULL_VAL; - return; - } - - ObjModule* module = AS_MODULE(moduleValue); - ObjList* names = wrenNewList(vm, module->variableNames.count); - vm->apiStack[0] = OBJ_VAL(names); - - // Initialize the elements to null in case a collection happens when we - // allocate the strings below. - for (int i = 0; i < names->elements.count; i++) - { - names->elements.data[i] = NULL_VAL; - } - - for (int i = 0; i < names->elements.count; i++) - { - names->elements.data[i] = OBJ_VAL(module->variableNames.data[i]); - } -} - -const char* wrenMetaSource() -{ - return metaModuleSource; -} - -WrenForeignMethodFn wrenMetaBindForeignMethod(WrenVM* vm, - const char* className, - bool isStatic, - const char* signature) -{ - // There is only one foreign method in the meta module. - ASSERT(strcmp(className, "Meta") == 0, "Should be in Meta class."); - ASSERT(isStatic, "Should be static."); - - if (strcmp(signature, "compile_(_,_,_)") == 0) - { - return metaCompile; - } - - if (strcmp(signature, "getModuleVariables_(_)") == 0) - { - return metaGetModuleVariables; - } - - ASSERT(false, "Unknown method."); - return NULL; -} - -#endif -// End file "wren_opt_meta.c" - -// End of wren.c - diff --git a/socket.wren b/socket.wren index 592fe60..63d4ea2 100644 --- a/socket.wren +++ b/socket.wren @@ -9,79 +9,371 @@ foreign class Socket { foreign static listen_(sock, backlog, callback) foreign static accept_(sock, callback) foreign static read_(sock, length, callback) + foreign static readUntil_(sock, bytes, callback) + foreign static readExactly_(sock, length, callback) foreign static write_(sock, data, callback) - // Additional raw functions can be added here following the pattern. + foreign static isReadable_(sock, callback) + foreign static select_(sockets, callback) + foreign static setNonBlocking(fd, flag) + foreign static close_(sock, callback) } class SocketInstance { - construct new(socketFd) { + construct fromFd(socketFd) { _sock = socketFd + _closed = false } + sock { _sock } + closed { _closed } + // Instance methods providing a more convenient API. accept() { - var fiber = Fiber.current - Socket.accept_(_sock) { |err, newSock| - fiber.transfer([err, newSock]) + if (_closed) { + return ["Socket is closed", null] } - return Fiber.yield() + var result = null + var done = false + + Socket.accept_(_sock, Fn.new { |err, newSock| + if (err) { + result = [err, null] + } else { + result = [null, newSock] + } + done = true + }) + + var attempts = 0 + while (!done && attempts < 1000) { + attempts = attempts + 1 + Fiber.yield() + } + + if (!done) { + return ["Accept operation timed out", null] + } + return result } read(length) { - var fiber = Fiber.current - Socket.read_(_sock, length) { |err, data| - fiber.transfer([err, data]) + if (_closed) { + return ["Socket is closed", null] } - return Fiber.yield() + if (length <= 0) { + return ["Invalid read length", null] + } + var result = null + var done = false + + Socket.read_(_sock, length, Fn.new { |err, data| + result = [err, data] + done = true + }) + + var attempts = 0 + while (!done && attempts < 1000) { + attempts = attempts + 1 + Fiber.yield() + } + + if (!done) { + return ["Read operation timed out", null] + } + return result + } + + readUntil(bytes) { + if (_closed) { + return ["Socket is closed", null] + } + if (!bytes || bytes.count == 0) { + return ["Invalid until bytes", null] + } + var result = null + var done = false + + Socket.readUntil_(_sock, bytes, Fn.new { |err, data| + result = [err, data] + done = true + }) + + var attempts = 0 + while (!done && attempts < 1000) { + attempts = attempts + 1 + Fiber.yield() + } + + if (!done) { + return ["ReadUntil operation timed out", null] + } + return result + } + + readExactly(length) { + if (_closed) { + return ["Socket is closed", null] + } + if (length <= 0) { + return ["Invalid read length", null] + } + var result = null + var done = false + + Socket.readExactly_(_sock, length, Fn.new { |err, data| + result = [err, data] + done = true + }) + + var attempts = 0 + while (!done && attempts < 1000) { + attempts = attempts + 1 + Fiber.yield() + } + + if (!done) { + return ["ReadExactly operation timed out", null] + } + return result } write(data) { - var fiber = Fiber.current - Socket.write_(_sock, data) { |err, nothing| - fiber.transfer(err) + if (_closed) { + return "Socket is closed" } - return Fiber.yield() + if (!data) { + return "Invalid write data" + } + var result = null + var done = false + + Socket.write_(_sock, data, Fn.new { |err, nothing| + result = err + done = true + }) + + var attempts = 0 + while (!done && attempts < 1000) { + attempts = attempts + 1 + Fiber.yield() + } + + if (!done) { + return "Write operation timed out" + } + return result + } + + isReadable() { + if (_closed) { + return [false, "Socket is closed"] + } + var result = null + var done = false + + Socket.isReadable_(_sock, Fn.new { |err, readable| + result = [err, readable] + done = true + }) + + var attempts = 0 + while (!done && attempts < 1000) { + attempts = attempts + 1 + Fiber.yield() + } + + if (!done) { + return [false, "IsReadable operation timed out"] + } + return result + } + + close() { + if (_closed) { + return null + } + + var result = "Close operation did not complete" + var done = false + + Socket.close_(_sock, Fn.new { |err, nothing| + result = err + done = true + }) + + var attempts = 0 + while (!done && attempts < 1000) { + attempts = attempts + 1 + Fiber.yield() + } + + _closed = true + return result + } + + bind(host, port) { + if (_closed) { + return ["Socket is closed", false] + } + if (port < 0 || port > 65535) { + return ["Invalid port", false] + } + + var result = ["Bind operation did not complete", false] + var done = false + + var bindHost = host + if (!bindHost) { + bindHost = "0.0.0.0" + } + + Socket.bind_(_sock, bindHost, port, Fn.new { |err, success| + if (err) { + result = [err, false] + } else { + result = [null, success] + } + done = true + }) + + var attempts = 0 + while (!done && attempts < 1000) { + attempts = attempts + 1 + Fiber.yield() + } + + return result + } + + listen(backlog) { + if (_closed) { + return ["Socket is closed", false] + } + if (backlog < 0) { + backlog = 128 + } + + var result = ["Listen operation did not complete", false] + var done = false + + Socket.listen_(_sock, backlog, Fn.new { |err, success| + if (err) { + result = [err, false] + } else { + result = [null, success] + } + done = true + }) + + var attempts = 0 + while (!done && attempts < 1000) { + attempts = attempts + 1 + Fiber.yield() + } + + return result } // Static methods for creating client and server sockets. static connect(host, port) { - var fiber = Fiber.current - Socket.connect_(host, port) { |err, sock| - if (err) { - fiber.transfer([err, null]) - } else { - fiber.transfer([null, SocketInstance.new(sock)]) - } + if (!host || port < 0 || port > 65535) { + return ["Invalid host or port", null] } - return Fiber.yield() + var result = null + var done = false + + Socket.connect_(host, port, Fn.new { |err, sock| + if (err) { + result = [err, null] + } else { + result = [null, SocketInstance.fromFd(sock)] + } + done = true + }) + + var attempts = 0 + while (!done && attempts < 1000) { + attempts = attempts + 1 + Fiber.yield() + } + + if (!done) { + return ["Connect operation timed out", null] + } + return result } static new() { - var fiber = Fiber.current - Socket.new_() { |err, sock| + var result = null + var done = false + + Socket.new_(Fn.new { |err, sock| if (err) { - fiber.transfer([err, null]) + result = [err, null] } else { - fiber.transfer([null, SocketInstance.new(sock)]) + result = [null, SocketInstance.fromFd(sock)] + } + done = true + }) + + var attempts = 0 + while (!done && attempts < 1000) { + attempts = attempts + 1 + Fiber.yield() + } + + if (!done) { + return ["Socket creation timed out", null] + } + return result + } + + static select(sockets, timeoutMs) { + if (!sockets || sockets.count == 0) { + return [[], null] + } + + var sockFds = [] + for (sock in sockets) { + if (sock is SocketInstance && !sock.closed) { + sockFds.add(sock.sock) } } - return Fiber.yield() - } - - bind(host, port) { - var fiber = Fiber.current - Socket.bind_(_sock, host, port) { |err, success| - fiber.transfer([err, success]) + + if (sockFds.count == 0) { + return [[], null] } - return Fiber.yield() - } - - listen(backlog) { - var fiber = Fiber.current - Socket.listen_(_sock, backlog) { |err, success| - fiber.transfer([err, success]) + + var result = null + var done = false + + Socket.select_(sockFds, Fn.new { |err, readableFds| + if (err) { + result = [[], err] + } else { + var readableSockets = [] + for (fd in readableFds) { + for (sock in sockets) { + if (sock.sock == fd) { + readableSockets.add(sock) + break + } + } + } + result = [readableSockets, null] + } + done = true + }) + + var attempts = 0 + while (!done && attempts < 1000) { + attempts = attempts + 1 + Fiber.yield() } - return Fiber.yield() + + if (!done) { + return [[], "Select operation timed out"] + } + return result } } - diff --git a/socket_backend.c b/socket_backend.c index 6effe7c..85d4dc9 100644 --- a/socket_backend.c +++ b/socket_backend.c @@ -3,6 +3,8 @@ #include #include #include +#include +#include #ifdef _WIN32 #include @@ -12,22 +14,31 @@ typedef HANDLE thread_t; typedef CRITICAL_SECTION mutex_t; typedef CONDITION_VARIABLE cond_t; + #define INVALID_SOCKET_HANDLE INVALID_SOCKET + #define poll WSAPoll #else #include #include #include + #include #include #include - #include #include #include + #include + #include typedef int socket_t; typedef pthread_t thread_t; typedef pthread_mutex_t mutex_t; typedef pthread_cond_t cond_t; #define closesocket(s) close(s) + #define INVALID_SOCKET_HANDLE -1 #endif +#define MAX_READ_UNTIL (16LL * 1024 * 1024) // 16MB limit for read_until +#define MAX_BUFFER_SIZE (64LL * 1024 * 1024) // 64MB absolute max +#define CHUNK_SIZE 65536 // 64KB chunks for reading + // --- Data Structures --- typedef enum { @@ -41,12 +52,14 @@ typedef enum { SOCKET_OP_READ_EXACTLY, SOCKET_OP_WRITE, SOCKET_OP_IS_READABLE, - SOCKET_OP_SELECT + SOCKET_OP_SELECT, + SOCKET_OP_CLOSE } SocketOp; typedef struct { char* data; - int length; + size_t length; + size_t capacity; } Buffer; typedef struct SocketContext { @@ -59,18 +72,21 @@ typedef struct SocketContext { char* host; int port; int backlog; - int length; + size_t length; Buffer write_data; char* until_bytes; - int until_len; - WrenHandle* sockets_list_handle; // For select + size_t until_len; + socket_t* sockets; + int sockets_count; // Result data bool success; char* error_message; socket_t new_sock; Buffer read_data; - WrenHandle* readable_sockets_handle; // For select result + bool result_bool; + socket_t* readable_sockets; + int readable_count; struct SocketContext* next; } SocketContext; @@ -81,81 +97,127 @@ typedef struct { SocketContext *head, *tail; mutex_t mutex; cond_t cond; + volatile bool shutdown; } SocketThreadSafeQueue; -void socket_queue_init(SocketThreadSafeQueue* q) { +static void socket_queue_init(SocketThreadSafeQueue* q) { + if (!q) return; q->head = q->tail = NULL; - #ifdef _WIN32 - InitializeCriticalSection(&q->mutex); - InitializeConditionVariable(&q->cond); - #else - pthread_mutex_init(&q->mutex, NULL); - pthread_cond_init(&q->cond, NULL); - #endif + q->shutdown = false; +#ifdef _WIN32 + InitializeCriticalSection(&q->mutex); + InitializeConditionVariable(&q->cond); +#else + pthread_mutex_init(&q->mutex, NULL); + pthread_cond_init(&q->cond, NULL); +#endif } -void socket_queue_destroy(SocketThreadSafeQueue* q) { - #ifdef _WIN32 - DeleteCriticalSection(&q->mutex); - #else - pthread_mutex_destroy(&q->mutex); - pthread_cond_destroy(&q->cond); - #endif +static void socket_queue_destroy(SocketThreadSafeQueue* q) { + if (!q) return; +#ifdef _WIN32 + DeleteCriticalSection(&q->mutex); +#else + pthread_mutex_destroy(&q->mutex); + pthread_cond_destroy(&q->cond); +#endif } -void socket_queue_push(SocketThreadSafeQueue* q, SocketContext* context) { - #ifdef _WIN32 - EnterCriticalSection(&q->mutex); - #else - pthread_mutex_lock(&q->mutex); - #endif - if(context) context->next = NULL; +static void socket_queue_push(SocketThreadSafeQueue* q, SocketContext* context) { + if (!q) return; +#ifdef _WIN32 + EnterCriticalSection(&q->mutex); +#else + pthread_mutex_lock(&q->mutex); +#endif + + if (q->shutdown) { +#ifdef _WIN32 + LeaveCriticalSection(&q->mutex); +#else + pthread_mutex_unlock(&q->mutex); +#endif + return; + } + + if (context) context->next = NULL; if (q->tail) q->tail->next = context; else q->head = context; q->tail = context; - #ifdef _WIN32 - WakeConditionVariable(&q->cond); - LeaveCriticalSection(&q->mutex); - #else - pthread_cond_signal(&q->cond); - pthread_mutex_unlock(&q->mutex); - #endif + +#ifdef _WIN32 + WakeConditionVariable(&q->cond); + LeaveCriticalSection(&q->mutex); +#else + pthread_cond_signal(&q->cond); + pthread_mutex_unlock(&q->mutex); +#endif } -SocketContext* socket_queue_pop(SocketThreadSafeQueue* q) { - #ifdef _WIN32 - EnterCriticalSection(&q->mutex); - while (q->head == NULL) { - SleepConditionVariableCS(&q->cond, &q->mutex, INFINITE); - } - #else - pthread_mutex_lock(&q->mutex); - while (q->head == NULL) { - pthread_cond_wait(&q->cond, &q->mutex); - } - #endif - SocketContext* context = q->head; - q->head = q->head->next; - if (q->head == NULL) q->tail = NULL; - #ifdef _WIN32 +static SocketContext* socket_queue_pop(SocketThreadSafeQueue* q) { + if (!q) return NULL; +#ifdef _WIN32 + EnterCriticalSection(&q->mutex); + while (q->head == NULL && !q->shutdown) { + SleepConditionVariableCS(&q->cond, &q->mutex, INFINITE); + } +#else + pthread_mutex_lock(&q->mutex); + while (q->head == NULL && !q->shutdown) { + pthread_cond_wait(&q->cond, &q->mutex); + } +#endif + + if (q->shutdown && q->head == NULL) { +#ifdef _WIN32 LeaveCriticalSection(&q->mutex); - #else +#else pthread_mutex_unlock(&q->mutex); - #endif +#endif + return NULL; + } + + SocketContext* context = q->head; + if (context) { + q->head = q->head->next; + if (q->head == NULL) q->tail = NULL; + } + +#ifdef _WIN32 + LeaveCriticalSection(&q->mutex); +#else + pthread_mutex_unlock(&q->mutex); +#endif return context; } -bool socket_queue_empty(SocketThreadSafeQueue* q) { +static void socket_queue_shutdown(SocketThreadSafeQueue* q) { + if (!q) return; +#ifdef _WIN32 + EnterCriticalSection(&q->mutex); + q->shutdown = true; + WakeAllConditionVariable(&q->cond); + LeaveCriticalSection(&q->mutex); +#else + pthread_mutex_lock(&q->mutex); + q->shutdown = true; + pthread_cond_broadcast(&q->cond); + pthread_mutex_unlock(&q->mutex); +#endif +} + +static bool socket_queue_empty(SocketThreadSafeQueue* q) { + if (!q) return true; bool empty; - #ifdef _WIN32 - EnterCriticalSection(&q->mutex); - empty = (q->head == NULL); - LeaveCriticalSection(&q->mutex); - #else - pthread_mutex_lock(&q->mutex); - empty = (q->head == NULL); - pthread_mutex_unlock(&q->mutex); - #endif +#ifdef _WIN32 + EnterCriticalSection(&q->mutex); + empty = (q->head == NULL); + LeaveCriticalSection(&q->mutex); +#else + pthread_mutex_lock(&q->mutex); + empty = (q->head == NULL); + pthread_mutex_unlock(&q->mutex); +#endif return empty; } @@ -164,134 +226,271 @@ bool socket_queue_empty(SocketThreadSafeQueue* q) { typedef struct { WrenVM* vm; volatile bool running; - thread_t threads[4]; // 4 worker threads + thread_t threads[16]; SocketThreadSafeQueue requestQueue; SocketThreadSafeQueue completionQueue; } AsyncSocketManager; static AsyncSocketManager* socketManager = NULL; -void free_socket_context(SocketContext* context) { +static void free_buffer(Buffer* buf) { + if (buf && buf->data) { + free(buf->data); + buf->data = NULL; + buf->length = 0; + buf->capacity = 0; + } +} + +static void free_socket_context(SocketContext* context) { if (context == NULL) return; - free(context->host); - free(context->error_message); - free(context->write_data.data); - free(context->read_data.data); - free(context->until_bytes); - if (context->callback) wrenReleaseHandle(context->vm, context->callback); - if (context->sockets_list_handle) wrenReleaseHandle(context->vm, context->sockets_list_handle); - if (context->readable_sockets_handle) wrenReleaseHandle(context->vm, context->readable_sockets_handle); + + if (context->host) { + free(context->host); + context->host = NULL; + } + if (context->error_message) { + free(context->error_message); + context->error_message = NULL; + } + free_buffer(&context->write_data); + free_buffer(&context->read_data); + if (context->until_bytes) { + free(context->until_bytes); + context->until_bytes = NULL; + } + if (context->sockets) { + free(context->sockets); + context->sockets = NULL; + } + if (context->readable_sockets) { + free(context->readable_sockets); + context->readable_sockets = NULL; + } + if (context->vm && context->callback) { + wrenReleaseHandle(context->vm, context->callback); + context->callback = NULL; + } + memset(context, 0, sizeof(SocketContext)); free(context); } static void set_socket_context_error(SocketContext* context, const char* message) { if (context == NULL) return; context->success = false; - if (context->error_message) free(context->error_message); - context->error_message = message ? strdup(message) : strdup("An unknown socket error occurred."); + if (context->error_message) { + free(context->error_message); + context->error_message = NULL; + } + if (message) { + size_t len = strlen(message); + if (len > 1024) len = 1024; // Limit error message size + context->error_message = (char*)malloc(len + 1); + if (context->error_message) { + strncpy(context->error_message, message, len); + context->error_message[len] = '\0'; + } + } else { + context->error_message = strdup("Unknown socket error"); + } +} + +static void set_socket_context_error_errno(SocketContext* context, const char* prefix) { + if (context == NULL) return; + char buf[512]; +#ifdef _WIN32 + int err = WSAGetLastError(); + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&buf, 0, NULL); + set_socket_context_error(context, buf); +#else + snprintf(buf, sizeof(buf), "%s: %s", prefix ? prefix : "Socket error", strerror(errno)); + set_socket_context_error(context, buf); +#endif +} + +static bool set_socket_nonblocking(socket_t sock) { +#ifdef _WIN32 + u_long mode = 1; + return ioctlsocket(sock, FIONBIO, &mode) == 0; +#else + int flags = fcntl(sock, F_GETFL, 0); + if (flags == -1) return false; + return fcntl(sock, F_SETFL, flags | O_NONBLOCK) != -1; +#endif } #ifdef _WIN32 -DWORD WINAPI socketWorkerThread(LPVOID arg); +static DWORD WINAPI socketWorkerThread(LPVOID arg); #else -void* socketWorkerThread(void* arg); +static void* socketWorkerThread(void* arg); #endif -void socketManager_create(WrenVM* vm) { - if (socketManager != NULL) return; - socketManager = (AsyncSocketManager*)malloc(sizeof(AsyncSocketManager)); +static void socketManager_create(WrenVM* vm) { + if (socketManager != NULL || !vm) return; + +#ifndef _WIN32 + // Ignore SIGPIPE to prevent crashes on broken pipe + signal(SIGPIPE, SIG_IGN); +#endif + + socketManager = (AsyncSocketManager*)calloc(1, sizeof(AsyncSocketManager)); if (socketManager == NULL) return; - #ifdef _WIN32 - WSADATA wsaData; - WSAStartup(MAKEWORD(2, 2), &wsaData); - #endif +#ifdef _WIN32 + WSADATA wsaData; + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { + free(socketManager); + socketManager = NULL; + return; + } +#endif socketManager->vm = vm; socketManager->running = true; socket_queue_init(&socketManager->requestQueue); socket_queue_init(&socketManager->completionQueue); - for (int i = 0; i < 4; ++i) { - #ifdef _WIN32 - socketManager->threads[i] = CreateThread(NULL, 0, socketWorkerThread, socketManager, 0, NULL); - #else - pthread_create(&socketManager->threads[i], NULL, socketWorkerThread, socketManager); - #endif + + for (int i = 0; i < 16; ++i) { +#ifdef _WIN32 + socketManager->threads[i] = CreateThread(NULL, 0, socketWorkerThread, socketManager, 0, NULL); + if (socketManager->threads[i] == NULL) { + socketManager->running = false; + socket_queue_shutdown(&socketManager->requestQueue); + socket_queue_shutdown(&socketManager->completionQueue); + for (int j = 0; j < i; ++j) { + if (socketManager->threads[j]) { + WaitForSingleObject(socketManager->threads[j], 5000); + CloseHandle(socketManager->threads[j]); + } + } + socket_queue_destroy(&socketManager->requestQueue); + socket_queue_destroy(&socketManager->completionQueue); + free(socketManager); + socketManager = NULL; + return; + } +#else + if (pthread_create(&socketManager->threads[i], NULL, socketWorkerThread, socketManager) != 0) { + socketManager->running = false; + socket_queue_shutdown(&socketManager->requestQueue); + socket_queue_shutdown(&socketManager->completionQueue); + for (int j = 0; j < i; ++j) { + pthread_join(socketManager->threads[j], NULL); + } + socket_queue_destroy(&socketManager->requestQueue); + socket_queue_destroy(&socketManager->completionQueue); + free(socketManager); + socketManager = NULL; + return; + } +#endif } } -void socketManager_destroy() { +static void socketManager_destroy() { if (!socketManager) return; + socketManager->running = false; - for (int i = 0; i < 4; ++i) socket_queue_push(&socketManager->requestQueue, NULL); - for (int i = 0; i < 4; ++i) { - #ifdef _WIN32 - WaitForSingleObject(socketManager->threads[i], INFINITE); + socket_queue_shutdown(&socketManager->requestQueue); + socket_queue_shutdown(&socketManager->completionQueue); + + for (int i = 0; i < 16; ++i) { +#ifdef _WIN32 + if (socketManager->threads[i] != NULL) { + WaitForSingleObject(socketManager->threads[i], 5000); CloseHandle(socketManager->threads[i]); - #else - pthread_join(socketManager->threads[i], NULL); - #endif + } +#else + pthread_join(socketManager->threads[i], NULL); +#endif } - while(!socket_queue_empty(&socketManager->requestQueue)) free_socket_context(socket_queue_pop(&socketManager->requestQueue)); - while(!socket_queue_empty(&socketManager->completionQueue)) free_socket_context(socket_queue_pop(&socketManager->completionQueue)); + + // Clean up remaining contexts + SocketContext* ctx; + while ((ctx = socket_queue_pop(&socketManager->requestQueue)) != NULL) { + free_socket_context(ctx); + } + while ((ctx = socket_queue_pop(&socketManager->completionQueue)) != NULL) { + free_socket_context(ctx); + } + socket_queue_destroy(&socketManager->requestQueue); socket_queue_destroy(&socketManager->completionQueue); free(socketManager); socketManager = NULL; - #ifdef _WIN32 - WSACleanup(); - #endif +#ifdef _WIN32 + WSACleanup(); +#endif } void socketManager_processCompletions() { - if (!socketManager || !socketManager->vm || socket_queue_empty(&socketManager->completionQueue)) return; - - while (!socket_queue_empty(&socketManager->completionQueue)) { + if (!socketManager || !socketManager->vm) return; + + int processed = 0; + const int max_per_cycle = 100; // Process max 100 completions per cycle + + while (!socket_queue_empty(&socketManager->completionQueue) && processed < max_per_cycle) { SocketContext* context = socket_queue_pop(&socketManager->completionQueue); if (context == NULL) continue; - + + processed++; + if (context->callback == NULL) { free_socket_context(context); continue; } WrenHandle* callHandle = wrenMakeCallHandle(socketManager->vm, "call(_,_)"); + if (!callHandle) { + free_socket_context(context); + continue; + } + wrenEnsureSlots(socketManager->vm, 3); wrenSetSlotHandle(socketManager->vm, 0, context->callback); if (context->success) { - wrenSetSlotNull(socketManager->vm, 1); // error is null + wrenSetSlotNull(socketManager->vm, 1); switch(context->operation) { case SOCKET_OP_CONNECT: case SOCKET_OP_NEW: case SOCKET_OP_ACCEPT: - wrenSetSlotDouble(socketManager->vm, 2, (double)context->new_sock); - break; + wrenSetSlotDouble(socketManager->vm, 2, (double)context->new_sock); + break; case SOCKET_OP_BIND: case SOCKET_OP_LISTEN: case SOCKET_OP_IS_READABLE: - wrenSetSlotBool(socketManager->vm, 2, true); - break; + wrenSetSlotBool(socketManager->vm, 2, context->result_bool); + break; case SOCKET_OP_READ: case SOCKET_OP_READ_UNTIL: case SOCKET_OP_READ_EXACTLY: - if (context->read_data.data) { + if (context->read_data.data && context->read_data.length > 0) { wrenSetSlotBytes(socketManager->vm, 2, context->read_data.data, context->read_data.length); - } else { - wrenSetSlotNull(socketManager->vm, 2); - } - break; - case SOCKET_OP_SELECT: - wrenSetSlotHandle(socketManager->vm, 2, context->readable_sockets_handle); + } else { + wrenSetSlotBytes(socketManager->vm, 2, "", 0); + } break; + case SOCKET_OP_SELECT: { + wrenSetSlotNewList(socketManager->vm, 2); + for (int i = 0; i < context->readable_count; ++i) { + wrenSetSlotDouble(socketManager->vm, 0, (double)context->readable_sockets[i]); + wrenInsertInList(socketManager->vm, 2, -1, 0); + } + break; + } case SOCKET_OP_WRITE: + case SOCKET_OP_CLOSE: default: wrenSetSlotNull(socketManager->vm, 2); break; } } else { - wrenSetSlotString(socketManager->vm, 1, context->error_message ? context->error_message : "Unknown error."); + wrenSetSlotString(socketManager->vm, 1, + context->error_message ? context->error_message : "Unknown error"); wrenSetSlotNull(socketManager->vm, 2); } @@ -304,109 +503,517 @@ void socketManager_processCompletions() { // --- Worker Thread Implementation --- #ifdef _WIN32 -DWORD WINAPI socketWorkerThread(LPVOID arg) { +static DWORD WINAPI socketWorkerThread(LPVOID arg) { #else -void* socketWorkerThread(void* arg) { +static void* socketWorkerThread(void* arg) { #endif AsyncSocketManager* manager = (AsyncSocketManager*)arg; + if (!manager) return 0; + while (manager->running) { SocketContext* context = socket_queue_pop(&manager->requestQueue); if (!context || !manager->running) { if (context) free_socket_context(context); - break; + continue; } + // Initialize result fields + context->success = false; + context->new_sock = INVALID_SOCKET_HANDLE; + context->result_bool = false; + context->readable_count = 0; + switch (context->operation) { case SOCKET_OP_NEW: { context->new_sock = socket(AF_INET, SOCK_STREAM, 0); - if (context->new_sock == -1) { - set_socket_context_error(context, "Failed to create socket."); + if (context->new_sock == INVALID_SOCKET_HANDLE) { + set_socket_context_error_errno(context, "Failed to create socket"); } else { - context->success = true; + int opt_val = 1; + if (setsockopt(context->new_sock, SOL_SOCKET, SO_REUSEADDR, + (const char*)&opt_val, sizeof(opt_val)) < 0) { + set_socket_context_error_errno(context, "Failed to set SO_REUSEADDR"); + closesocket(context->new_sock); + context->new_sock = INVALID_SOCKET_HANDLE; + } else if (!set_socket_nonblocking(context->new_sock)) { + set_socket_context_error_errno(context, "Failed to set non-blocking"); + closesocket(context->new_sock); + context->new_sock = INVALID_SOCKET_HANDLE; + } else { + // Set TCP_NODELAY for better responsiveness + int nodelay = 1; + setsockopt(context->new_sock, IPPROTO_TCP, TCP_NODELAY, + (const char*)&nodelay, sizeof(nodelay)); + context->success = true; + } } break; } + case SOCKET_OP_CONNECT: { - struct sockaddr_in serv_addr; + struct sockaddr_in serv_addr = {0}; context->new_sock = socket(AF_INET, SOCK_STREAM, 0); - if (context->new_sock < 0) { - set_socket_context_error(context, "Socket creation error"); + if (context->new_sock == INVALID_SOCKET_HANDLE) { + set_socket_context_error_errno(context, "Socket creation error"); break; } + + if (!set_socket_nonblocking(context->new_sock)) { + set_socket_context_error_errno(context, "Failed to set non-blocking"); + closesocket(context->new_sock); + context->new_sock = INVALID_SOCKET_HANDLE; + break; + } + serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(context->port); - if(inet_pton(AF_INET, context->host, &serv_addr.sin_addr)<=0) { - set_socket_context_error(context, "Invalid address/ Address not supported"); + if (inet_pton(AF_INET, context->host, &serv_addr.sin_addr) <= 0) { + set_socket_context_error(context, "Invalid address"); + closesocket(context->new_sock); + context->new_sock = INVALID_SOCKET_HANDLE; break; } - if (connect(context->new_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { - set_socket_context_error(context, "Connection Failed"); + + int ret = connect(context->new_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); + if (ret < 0) { +#ifdef _WIN32 + if (WSAGetLastError() != WSAEWOULDBLOCK) { +#else + if (errno != EINPROGRESS) { +#endif + set_socket_context_error_errno(context, "Connection failed"); + closesocket(context->new_sock); + context->new_sock = INVALID_SOCKET_HANDLE; + } else { + // Connection in progress, wait for completion + struct pollfd pfd = {context->new_sock, POLLOUT, 0}; + int poll_ret = poll(&pfd, 1, 5000); // 5 second timeout + if (poll_ret <= 0) { + set_socket_context_error(context, "Connection timeout"); + closesocket(context->new_sock); + context->new_sock = INVALID_SOCKET_HANDLE; + } else { + int error = 0; + socklen_t len = sizeof(error); + if (getsockopt(context->new_sock, SOL_SOCKET, SO_ERROR, + (char*)&error, &len) < 0 || error != 0) { + set_socket_context_error(context, "Connection failed"); + closesocket(context->new_sock); + context->new_sock = INVALID_SOCKET_HANDLE; + } else { + context->success = true; + } + } + } } else { context->success = true; } break; } + case SOCKET_OP_BIND: { - struct sockaddr_in address; + struct sockaddr_in address = {0}; address.sin_family = AF_INET; - address.sin_addr.s_addr = INADDR_ANY; // Or inet_addr(context->host) for specific interface address.sin_port = htons(context->port); - if (bind(context->sock, (struct sockaddr *)&address, sizeof(address)) < 0) { - set_socket_context_error(context, "Bind failed"); - } else { - context->success = true; - } - break; - } - case SOCKET_OP_LISTEN: { - if (listen(context->sock, context->backlog) < 0) { - set_socket_context_error(context, "Listen failed"); - } else { - context->success = true; - } - break; - } - case SOCKET_OP_ACCEPT: { - struct sockaddr_in address; - int addrlen = sizeof(address); - context->new_sock = accept(context->sock, (struct sockaddr *)&address, (socklen_t*)&addrlen); - if (context->new_sock < 0) { - set_socket_context_error(context, "Accept failed"); - } else { - context->success = true; - } - break; - } - case SOCKET_OP_READ: { - char* buffer = malloc(context->length + 1); - int valread = recv(context->sock, buffer, context->length, 0); - if (valread >= 0) { - context->read_data.data = buffer; - context->read_data.length = valread; - context->success = true; - } else { - free(buffer); - set_socket_context_error(context, "Read failed"); - } - break; - } - case SOCKET_OP_WRITE: { - int total_sent = 0; - while (total_sent < context->write_data.length) { - int sent = send(context->sock, context->write_data.data + total_sent, context->write_data.length - total_sent, 0); - if (sent < 0) { - set_socket_context_error(context, "Write failed"); + if (context->host && strlen(context->host) > 0) { + if (inet_pton(AF_INET, context->host, &address.sin_addr) <= 0) { + set_socket_context_error(context, "Invalid bind address"); break; } - total_sent += sent; + } else { + address.sin_addr.s_addr = INADDR_ANY; } - if (total_sent == context->write_data.length) { + if (bind(context->sock, (struct sockaddr *)&address, sizeof(address)) < 0) { + set_socket_context_error_errno(context, "Bind failed"); + } else { context->success = true; + context->result_bool = true; } break; } - // ... other cases ... + + case SOCKET_OP_LISTEN: { + if (listen(context->sock, context->backlog) < 0) { + set_socket_context_error_errno(context, "Listen failed"); + } else { + context->success = true; + context->result_bool = true; + } + break; + } + + case SOCKET_OP_ACCEPT: { + struct sockaddr_in address = {0}; + socklen_t addrlen = sizeof(address); + context->new_sock = accept(context->sock, (struct sockaddr *)&address, &addrlen); + if (context->new_sock == INVALID_SOCKET_HANDLE) { +#ifdef _WIN32 + if (WSAGetLastError() == WSAEWOULDBLOCK) { +#else + if (errno == EAGAIN || errno == EWOULDBLOCK) { +#endif + set_socket_context_error(context, "Would block"); + } else { + set_socket_context_error_errno(context, "Accept failed"); + } + } else { + if (!set_socket_nonblocking(context->new_sock)) { + closesocket(context->new_sock); + context->new_sock = INVALID_SOCKET_HANDLE; + set_socket_context_error(context, "Failed to set non-blocking on accepted socket"); + } else { + context->success = true; + } + } + break; + } + + case SOCKET_OP_READ: { + if (context->length <= 0 || context->length > MAX_BUFFER_SIZE) { + set_socket_context_error(context, "Invalid read length"); + break; + } + + size_t actual_size = context->length < CHUNK_SIZE ? context->length : CHUNK_SIZE; + char* buffer = (char*)malloc(actual_size); + if (!buffer) { + set_socket_context_error(context, "Out of memory"); + break; + } + + int valread = recv(context->sock, buffer, actual_size, 0); + if (valread > 0) { + context->read_data.data = buffer; + context->read_data.length = valread; + context->read_data.capacity = actual_size; + context->success = true; + } else if (valread == 0) { + free(buffer); + context->read_data.data = NULL; + context->read_data.length = 0; + context->success = true; // EOF is success with 0 bytes + } else { +#ifdef _WIN32 + if (WSAGetLastError() == WSAEWOULDBLOCK) { +#else + if (errno == EAGAIN || errno == EWOULDBLOCK) { +#endif + free(buffer); + context->read_data.data = NULL; + context->read_data.length = 0; + context->success = true; // Would block, return empty + } else { + set_socket_context_error_errno(context, "Read failed"); + free(buffer); + } + } + break; + } + + case SOCKET_OP_READ_UNTIL: { + if (!context->until_bytes || context->until_len <= 0 || context->until_len > 256) { + set_socket_context_error(context, "Invalid until bytes"); + break; + } + + Buffer buf = {0}; + buf.capacity = CHUNK_SIZE; + buf.data = (char*)malloc(buf.capacity); + if (!buf.data) { + set_socket_context_error(context, "Out of memory"); + break; + } + + bool found = false; + bool error = false; + + while (!found && !error && buf.length < MAX_READ_UNTIL) { + // Make sure we have room for more data + if (buf.length + CHUNK_SIZE > buf.capacity) { + size_t new_cap = buf.capacity * 2; + if (new_cap > MAX_READ_UNTIL) new_cap = MAX_READ_UNTIL; + char* new_data = (char*)realloc(buf.data, new_cap); + if (!new_data) { + set_socket_context_error(context, "Out of memory"); + error = true; + break; + } + buf.data = new_data; + buf.capacity = new_cap; + } + + size_t read_size = buf.capacity - buf.length; + if (read_size > CHUNK_SIZE) read_size = CHUNK_SIZE; + + int valread = recv(context->sock, buf.data + buf.length, read_size, 0); + if (valread > 0) { + buf.length += valread; + + // Search for until_bytes + if (buf.length >= context->until_len) { + for (size_t i = 0; i <= buf.length - context->until_len; ++i) { + if (memcmp(buf.data + i, context->until_bytes, context->until_len) == 0) { + context->read_data.data = buf.data; + context->read_data.length = i + context->until_len; + context->read_data.capacity = buf.capacity; + found = true; + buf.data = NULL; // Transfer ownership + break; + } + } + } + } else if (valread == 0) { + set_socket_context_error(context, "Unexpected EOF"); + error = true; + } else { +#ifdef _WIN32 + if (WSAGetLastError() != WSAEWOULDBLOCK) { +#else + if (errno != EAGAIN && errno != EWOULDBLOCK) { +#endif + set_socket_context_error_errno(context, "Read failed"); + error = true; + } else { + // Would block, wait a bit +#ifdef _WIN32 + Sleep(1); +#else + usleep(1000); +#endif + } + } + } + + if (!found && !error && buf.length >= MAX_READ_UNTIL) { + set_socket_context_error(context, "Data exceeds limit"); + error = true; + } + + if (buf.data) free(buf.data); + if (found) context->success = true; + break; + } + + case SOCKET_OP_READ_EXACTLY: { + if (context->length <= 0 || context->length > MAX_BUFFER_SIZE) { + set_socket_context_error(context, "Invalid read length"); + break; + } + + char* buffer = (char*)malloc(context->length); + if (!buffer) { + set_socket_context_error(context, "Out of memory"); + break; + } + + size_t total_read = 0; + int retries = 0; + const int max_retries = 1000; + + while (total_read < context->length && retries < max_retries) { + size_t to_read = context->length - total_read; + if (to_read > CHUNK_SIZE) to_read = CHUNK_SIZE; + + int valread = recv(context->sock, buffer + total_read, to_read, 0); + if (valread > 0) { + total_read += valread; + retries = 0; // Reset retries on successful read + } else if (valread == 0) { + set_socket_context_error(context, "Unexpected EOF"); + free(buffer); + total_read = 0; + break; + } else { +#ifdef _WIN32 + if (WSAGetLastError() == WSAEWOULDBLOCK) { +#else + if (errno == EAGAIN || errno == EWOULDBLOCK) { +#endif + retries++; +#ifdef _WIN32 + Sleep(1); +#else + usleep(1000); +#endif + } else { + set_socket_context_error_errno(context, "Read failed"); + free(buffer); + total_read = 0; + break; + } + } + } + + if (total_read == context->length) { + context->read_data.data = buffer; + context->read_data.length = total_read; + context->read_data.capacity = context->length; + context->success = true; + } else if (retries >= max_retries) { + set_socket_context_error(context, "Read timeout"); + free(buffer); + } + break; + } + + case SOCKET_OP_WRITE: { + if (!context->write_data.data || context->write_data.length <= 0) { + set_socket_context_error(context, "Invalid write data"); + break; + } + + if (context->write_data.length > MAX_BUFFER_SIZE) { + set_socket_context_error(context, "Write data too large"); + break; + } + + size_t total_sent = 0; + int retries = 0; + const int max_retries = 1000; + + while (total_sent < context->write_data.length && retries < max_retries) { + size_t to_send = context->write_data.length - total_sent; + if (to_send > CHUNK_SIZE) to_send = CHUNK_SIZE; + + int sent = send(context->sock, context->write_data.data + total_sent, to_send, +#ifdef _WIN32 + 0 +#else + MSG_NOSIGNAL +#endif + ); + + if (sent > 0) { + total_sent += sent; + retries = 0; + } else if (sent == 0) { + retries++; +#ifdef _WIN32 + Sleep(1); +#else + usleep(1000); +#endif + } else { +#ifdef _WIN32 + if (WSAGetLastError() == WSAEWOULDBLOCK) { +#else + if (errno == EAGAIN || errno == EWOULDBLOCK) { +#endif + retries++; +#ifdef _WIN32 + Sleep(1); +#else + usleep(1000); +#endif + } else { + set_socket_context_error_errno(context, "Write failed"); + break; + } + } + } + + if (total_sent == context->write_data.length) { + context->success = true; + } else if (retries >= max_retries) { + set_socket_context_error(context, "Write timeout"); + } + break; + } + + case SOCKET_OP_IS_READABLE: { + struct pollfd pfd = {context->sock, POLLIN, 0}; + int result = poll(&pfd, 1, 0); + if (result < 0) { + set_socket_context_error_errno(context, "Poll failed"); + } else { + context->success = true; + context->result_bool = (pfd.revents & POLLIN) != 0; + } + break; + } + + case SOCKET_OP_SELECT: { + if (!context->sockets || context->sockets_count <= 0 || context->sockets_count > 1024) { + set_socket_context_error(context, "Invalid sockets list"); + break; + } + + struct pollfd* pfds = (struct pollfd*)calloc(context->sockets_count, sizeof(struct pollfd)); + if (!pfds) { + set_socket_context_error(context, "Out of memory"); + break; + } + + int valid_fds = 0; + for (int i = 0; i < context->sockets_count; ++i) { + socket_t fd = context->sockets[i]; + if (fd != INVALID_SOCKET_HANDLE) { + pfds[i].fd = fd; + pfds[i].events = POLLIN; + pfds[i].revents = 0; + valid_fds++; + } else { + pfds[i].fd = -1; + } + } + + if (valid_fds == 0) { + set_socket_context_error(context, "No valid sockets"); + free(pfds); + break; + } + + int result = poll(pfds, context->sockets_count, 100); // 100ms timeout + if (result < 0) { + set_socket_context_error_errno(context, "Poll failed"); + free(pfds); + break; + } + + int rcount = 0; + for (int i = 0; i < context->sockets_count; ++i) { + if (pfds[i].fd != -1 && (pfds[i].revents & POLLIN)) { + rcount++; + } + } + + if (rcount > 0) { + context->readable_sockets = (socket_t*)malloc(rcount * sizeof(socket_t)); + if (!context->readable_sockets) { + set_socket_context_error(context, "Out of memory"); + free(pfds); + break; + } + + int j = 0; + for (int i = 0; i < context->sockets_count && j < rcount; ++i) { + if (pfds[i].fd != -1 && (pfds[i].revents & POLLIN)) { + context->readable_sockets[j++] = pfds[i].fd; + } + } + context->readable_count = j; + } else { + context->readable_count = 0; + } + + free(pfds); + context->success = true; + break; + } + + case SOCKET_OP_CLOSE: { + if (context->sock != INVALID_SOCKET_HANDLE) { + closesocket(context->sock); + } + context->success = true; + break; + } + + default: + set_socket_context_error(context, "Unsupported operation"); + break; } + socket_queue_push(&manager->completionQueue, context); } return 0; @@ -415,69 +1022,232 @@ void* socketWorkerThread(void* arg) { // --- Wren FFI Functions --- static void create_socket_context(WrenVM* vm, SocketOp op) { - SocketContext* context = (SocketContext*)calloc(1, sizeof(SocketContext)); - if (!context) { - wrenSetSlotString(vm, 0, "Out of memory."); + if (!vm || !socketManager) { + wrenSetSlotString(vm, 0, "Socket manager not initialized"); wrenAbortFiber(vm, 0); return; } + + SocketContext* context = (SocketContext*)calloc(1, sizeof(SocketContext)); + if (!context) { + wrenSetSlotString(vm, 0, "Out of memory"); + wrenAbortFiber(vm, 0); + return; + } + context->vm = vm; context->operation = op; + context->sock = INVALID_SOCKET_HANDLE; + context->new_sock = INVALID_SOCKET_HANDLE; + bool valid = true; + + // Validate and extract parameters based on operation switch(op) { case SOCKET_OP_CONNECT: + if (wrenGetSlotType(vm, 1) != WREN_TYPE_STRING) { + valid = false; + break; + } context->host = strdup(wrenGetSlotString(vm, 1)); + if (!context->host) valid = false; context->port = (int)wrenGetSlotDouble(vm, 2); + if (context->port < 0 || context->port > 65535) valid = false; context->callback = wrenGetSlotHandle(vm, 3); break; + case SOCKET_OP_NEW: + // Get callback from slot 1 + if (wrenGetSlotType(vm, 1) == WREN_TYPE_NULL) { + free_socket_context(context); + wrenSetSlotString(vm, 0, "Callback cannot be null"); + wrenAbortFiber(vm, 0); + return; + } context->callback = wrenGetSlotHandle(vm, 1); + if (!context->callback) { + free_socket_context(context); + wrenSetSlotString(vm, 0, "Failed to get callback handle"); + wrenAbortFiber(vm, 0); + return; + } break; + case SOCKET_OP_BIND: context->sock = (socket_t)wrenGetSlotDouble(vm, 1); + if (wrenGetSlotType(vm, 2) != WREN_TYPE_STRING) { + valid = false; + break; + } context->host = strdup(wrenGetSlotString(vm, 2)); context->port = (int)wrenGetSlotDouble(vm, 3); + if (context->port < 0 || context->port > 65535) valid = false; context->callback = wrenGetSlotHandle(vm, 4); break; + case SOCKET_OP_LISTEN: context->sock = (socket_t)wrenGetSlotDouble(vm, 1); context->backlog = (int)wrenGetSlotDouble(vm, 2); + if (context->backlog < 0 || context->backlog > 1024) context->backlog = 128; context->callback = wrenGetSlotHandle(vm, 3); break; + case SOCKET_OP_ACCEPT: context->sock = (socket_t)wrenGetSlotDouble(vm, 1); context->callback = wrenGetSlotHandle(vm, 2); break; + case SOCKET_OP_READ: context->sock = (socket_t)wrenGetSlotDouble(vm, 1); - context->length = (int)wrenGetSlotDouble(vm, 2); + context->length = (size_t)wrenGetSlotDouble(vm, 2); + if (context->length <= 0 || context->length > MAX_BUFFER_SIZE) valid = false; context->callback = wrenGetSlotHandle(vm, 3); break; - case SOCKET_OP_WRITE: + + case SOCKET_OP_READ_UNTIL: { + context->sock = (socket_t)wrenGetSlotDouble(vm, 1); + int ulen; + const char* udata = wrenGetSlotBytes(vm, 2, &ulen); + if (!udata || ulen <= 0 || ulen > 256) { + valid = false; + break; + } + context->until_bytes = (char*)malloc(ulen); + if (context->until_bytes) { + memcpy(context->until_bytes, udata, ulen); + context->until_len = ulen; + } else { + valid = false; + } + context->callback = wrenGetSlotHandle(vm, 3); + break; + } + + case SOCKET_OP_READ_EXACTLY: + context->sock = (socket_t)wrenGetSlotDouble(vm, 1); + context->length = (size_t)wrenGetSlotDouble(vm, 2); + if (context->length <= 0 || context->length > MAX_BUFFER_SIZE) valid = false; + context->callback = wrenGetSlotHandle(vm, 3); + break; + + case SOCKET_OP_WRITE: { context->sock = (socket_t)wrenGetSlotDouble(vm, 1); int len; const char* data = wrenGetSlotBytes(vm, 2, &len); - context->write_data.data = malloc(len); - memcpy(context->write_data.data, data, len); - context->write_data.length = len; + if (!data || len <= 0 || len > MAX_BUFFER_SIZE) { + valid = false; + break; + } + context->write_data.data = (char*)malloc(len); + if (context->write_data.data) { + memcpy(context->write_data.data, data, len); + context->write_data.length = len; + context->write_data.capacity = len; + } else { + valid = false; + } context->callback = wrenGetSlotHandle(vm, 3); break; - // ... other cases ... + } + + case SOCKET_OP_IS_READABLE: + context->sock = (socket_t)wrenGetSlotDouble(vm, 1); + context->callback = wrenGetSlotHandle(vm, 2); + break; + + case SOCKET_OP_SELECT: { + if (wrenGetSlotType(vm, 1) != WREN_TYPE_LIST) { + valid = false; + break; + } + int count = wrenGetListCount(vm, 1); + if (count <= 0 || count > 1024) { + valid = false; + break; + } + context->sockets = (socket_t*)malloc(count * sizeof(socket_t)); + if (!context->sockets) { + valid = false; + } else { + context->sockets_count = count; + for (int i = 0; i < count; ++i) { + wrenGetListElement(vm, 1, i, 0); + context->sockets[i] = (socket_t)wrenGetSlotDouble(vm, 0); + } + } + context->callback = wrenGetSlotHandle(vm, 2); + break; + } + + case SOCKET_OP_CLOSE: + context->sock = (socket_t)wrenGetSlotDouble(vm, 1); + context->callback = wrenGetSlotHandle(vm, 2); + break; + default: - free(context); - return; + valid = false; + break; } + + if (!valid || !context->callback) { + free_socket_context(context); + wrenSetSlotString(vm, 0, "Invalid context creation"); + wrenAbortFiber(vm, 0); + return; + } + socket_queue_push(&socketManager->requestQueue, context); } -void socketConnect(WrenVM* vm) { create_socket_context(vm, SOCKET_OP_CONNECT); } -void socketNew(WrenVM* vm) { create_socket_context(vm, SOCKET_OP_NEW); } -void socketBind(WrenVM* vm) { create_socket_context(vm, SOCKET_OP_BIND); } -void socketListen(WrenVM* vm) { create_socket_context(vm, SOCKET_OP_LISTEN); } -void socketAccept(WrenVM* vm) { create_socket_context(vm, SOCKET_OP_ACCEPT); } -void socketRead(WrenVM* vm) { create_socket_context(vm, SOCKET_OP_READ); } -void socketWrite(WrenVM* vm) { create_socket_context(vm, SOCKET_OP_WRITE); } -// ... other FFI functions ... +static void socketSetNonBlocking(WrenVM* vm) { + socket_t fd = (socket_t)wrenGetSlotDouble(vm, 0); + bool flag = wrenGetSlotBool(vm, 1); + +#ifdef _WIN32 + u_long mode = flag ? 1 : 0; + wrenSetSlotBool(vm, 0, ioctlsocket(fd, FIONBIO, &mode) == 0); +#else + int flags = fcntl(fd, F_GETFL, 0); + if (flags == -1) { + wrenSetSlotBool(vm, 0, false); + return; + } + if (flag) { + flags |= O_NONBLOCK; + } else { + flags &= ~O_NONBLOCK; + } + wrenSetSlotBool(vm, 0, fcntl(fd, F_SETFL, flags) != -1); +#endif +} + +static void socketConnect(WrenVM* vm) { create_socket_context(vm, SOCKET_OP_CONNECT); } +static void socketNew(WrenVM* vm) { + if (!vm || !socketManager) { + wrenSetSlotString(vm, 0, "Socket manager not initialized"); + wrenAbortFiber(vm, 0); + return; + } + + // Validate callback parameter + if (wrenGetSlotType(vm, 1) != WREN_TYPE_NULL && wrenGetSlotHandle(vm, 1) == NULL) { + wrenSetSlotString(vm, 0, "Invalid callback parameter"); + wrenAbortFiber(vm, 0); + return; + } + + create_socket_context(vm, SOCKET_OP_NEW); +} +static void socketBind(WrenVM* vm) { create_socket_context(vm, SOCKET_OP_BIND); } +static void socketListen(WrenVM* vm) { create_socket_context(vm, SOCKET_OP_LISTEN); } +static void socketAccept(WrenVM* vm) { create_socket_context(vm, SOCKET_OP_ACCEPT); } +static void socketRead(WrenVM* vm) { create_socket_context(vm, SOCKET_OP_READ); } +static void socketReadUntil(WrenVM* vm) { create_socket_context(vm, SOCKET_OP_READ_UNTIL); } +static void socketReadExactly(WrenVM* vm) { create_socket_context(vm, SOCKET_OP_READ_EXACTLY); } +static void socketWrite(WrenVM* vm) { create_socket_context(vm, SOCKET_OP_WRITE); } +static void socketIsReadable(WrenVM* vm) { create_socket_context(vm, SOCKET_OP_IS_READABLE); } +static void socketSelect(WrenVM* vm) { create_socket_context(vm, SOCKET_OP_SELECT); } +static void socketClose(WrenVM* vm) { create_socket_context(vm, SOCKET_OP_CLOSE); } WrenForeignMethodFn bindSocketForeignMethod(WrenVM* vm, const char* module, const char* className, bool isStatic, const char* signature) { if (strcmp(module, "socket") != 0) return NULL; @@ -488,14 +1258,18 @@ WrenForeignMethodFn bindSocketForeignMethod(WrenVM* vm, const char* module, cons if (strcmp(signature, "listen_(_,_,_)") == 0) return socketListen; if (strcmp(signature, "accept_(_,_)") == 0) return socketAccept; if (strcmp(signature, "read_(_,_,_)") == 0) return socketRead; + if (strcmp(signature, "readUntil_(_,_,_)") == 0) return socketReadUntil; + if (strcmp(signature, "readExactly_(_,_,_)") == 0) return socketReadExactly; if (strcmp(signature, "write_(_,_,_)") == 0) return socketWrite; - // ... other bindings ... + if (strcmp(signature, "isReadable_(_,_)") == 0) return socketIsReadable; + if (strcmp(signature, "select_(_,_)") == 0) return socketSelect; + if (strcmp(signature, "setNonBlocking(_,_)") == 0) return socketSetNonBlocking; + if (strcmp(signature, "close_(_,_)") == 0) return socketClose; } return NULL; } WrenForeignClassMethods bindSocketForeignClass(WrenVM* vm, const char* module, const char* className) { - WrenForeignClassMethods methods = {0, 0}; + WrenForeignClassMethods methods = {NULL, NULL}; return methods; } - diff --git a/socket_benchmark.wren b/socket_benchmark.wren new file mode 100644 index 0000000..b9efe02 --- /dev/null +++ b/socket_benchmark.wren @@ -0,0 +1,151 @@ +// fiber_pure_test.wren - Test pure Fiber performance without I/O + +foreign class Host { + foreign static signalDone() +} + +var mainFiber = Fiber.new { + System.print("=== Pure Fiber Performance Test ===\n") + + // Test 1: Minimal yield overhead + System.print("Test 1: Minimal Fiber.yield() cost") + var iterations = 100000 + var start = System.clock + for (i in 1..iterations) { + Fiber.yield() + } + var elapsed = System.clock - start + System.print(" %(iterations) yields: %(elapsed)s") + System.print(" Rate: %(iterations/elapsed) yields/sec") + System.print(" Per yield: %(elapsed/iterations * 1000000) microseconds\n") + + // Test 2: Fiber creation cost + System.print("Test 2: Fiber creation overhead") + iterations = 10000 + start = System.clock + for (i in 1..iterations) { + var f = Fiber.new { } + } + elapsed = System.clock - start + System.print(" %(iterations) fiber creations: %(elapsed)s") + System.print(" Rate: %(iterations/elapsed) fibers/sec") + System.print(" Per fiber: %(elapsed/iterations * 1000) milliseconds\n") + + // Test 3: Fiber with work + System.print("Test 3: Fiber with actual work") + var counter = 0 + var workFiber = Fiber.new { + for (i in 1..10000) { + counter = counter + 1 + if (i % 100 == 0) Fiber.yield() + } + } + + start = System.clock + while (!workFiber.isDone) { + workFiber.call() + } + elapsed = System.clock - start + System.print(" Processed %(counter) items in %(elapsed)s") + System.print(" Rate: %(counter/elapsed) items/sec\n") + + // Test 4: Multiple fibers cooperating + System.print("Test 4: Multiple fibers round-robin") + var fibers = [] + var shared = 0 + for (i in 1..100) { + fibers.add(Fiber.new { + for (j in 1..100) { + shared = shared + 1 + Fiber.yield() + } + }) + } + + start = System.clock + var done = false + while (!done) { + done = true + for (f in fibers) { + if (!f.isDone) { + f.call() + done = false + } + } + } + elapsed = System.clock - start + System.print(" 100 fibers x 100 yields = %(shared) operations in %(elapsed)s") + System.print(" Rate: %(shared/elapsed) ops/sec\n") + + // Test 5: Producer-consumer pattern + System.print("Test 5: Producer-Consumer pattern") + var queue = [] + var produced = 0 + var consumed = 0 + + var producer = Fiber.new { + for (i in 1..1000) { + queue.add(i) + produced = produced + 1 + if (queue.count > 10) Fiber.yield() + } + } + + var consumer = Fiber.new { + while (consumed < 1000) { + if (queue.count > 0) { + var item = queue.removeAt(0) + consumed = consumed + 1 + } else { + Fiber.yield() + } + } + } + + start = System.clock + while (!producer.isDone || !consumer.isDone) { + if (!producer.isDone) producer.call() + if (!consumer.isDone) consumer.call() + } + elapsed = System.clock - start + System.print(" Produced: %(produced), Consumed: %(consumed)") + System.print(" Time: %(elapsed)s") + System.print(" Rate: %(consumed/elapsed) items/sec\n") + + // Test 6: Deep call stack + System.print("Test 6: Deep recursion with yields") + var depth = 0 + var maxDepth = 1000 + + var recursiveFiber = Fiber.new { + for (i in 1..maxDepth) { + depth = depth + 1 + Fiber.yield() + } + } + + start = System.clock + while (!recursiveFiber.isDone) { + recursiveFiber.call() + } + elapsed = System.clock - start + System.print(" Processed %(depth) yields: %(elapsed)s") + System.print(" Per yield: %(elapsed/depth * 1000000) microseconds\n") + + System.print("=== Analysis ===") + System.print("Based on these results, Wren fibers are:") + var yieldMicros = elapsed/iterations * 1000000 + if (yieldMicros < 1) { + System.print(" EXCELLENT - Sub-microsecond yields") + } else if (yieldMicros < 10) { + System.print(" GOOD - Single-digit microsecond yields") + } else if (yieldMicros < 50) { + System.print(" MODERATE - Tens of microseconds per yield") + } else { + System.print(" SLOW - Over 50 microseconds per yield") + System.print(" This suggests FFI overhead or implementation issues") + } + + Host.signalDone() + while(true) { Fiber.yield() } +} diff --git a/socket_benchmark2.wren b/socket_benchmark2.wren new file mode 100644 index 0000000..e5a8dcf --- /dev/null +++ b/socket_benchmark2.wren @@ -0,0 +1,152 @@ +// fiber_pure_test.wren - Test pure Fiber performance without I/O + +foreign class Host { + foreign static signalDone() +} + +var mainFiber = Fiber.new { + System.print("=== Pure Fiber Performance Test ===\n") + + // Test 1: Minimal yield overhead + System.print("Test 1: Minimal Fiber.yield() cost") + var iterations = 100000 + var start = System.clock + for (i in 1..iterations) { + Fiber.yield() + } + var elapsed = System.clock - start + System.print(" %(iterations) yields: %(elapsed)s") + System.print(" Rate: %(iterations/elapsed) yields/sec") + System.print(" Per yield: %(elapsed/iterations * 1000000) microseconds\n") + + // Test 2: Fiber creation cost + System.print("Test 2: Fiber creation overhead") + iterations = 10000 + start = System.clock + for (i in 1..iterations) { + var f = Fiber.new { } + } + elapsed = System.clock - start + System.print(" %(iterations) fiber creations: %(elapsed)s") + System.print(" Rate: %(iterations/elapsed) fibers/sec") + System.print(" Per fiber: %(elapsed/iterations * 1000) milliseconds\n") + + // Test 3: Fiber with work + System.print("Test 3: Fiber with actual work") + var counter = 0 + var workFiber = Fiber.new { + for (i in 1..10000) { + counter = counter + 1 + if (i % 100 == 0) Fiber.yield() + } + } + + start = System.clock + while (!workFiber.isDone) { + workFiber.call() + } + elapsed = System.clock - start + System.print(" Processed %(counter) items in %(elapsed)s") + System.print(" Rate: %(counter/elapsed) items/sec\n") + + // Test 4: Multiple fibers cooperating + System.print("Test 4: Multiple fibers round-robin") + var fibers = [] + var shared = 0 + for (i in 1..100) { + fibers.add(Fiber.new { + for (j in 1..100) { + shared = shared + 1 + Fiber.yield() + } + }) + } + + start = System.clock + var done = false + while (!done) { + done = true + for (f in fibers) { + if (!f.isDone) { + f.call() + done = false + } + } + } + elapsed = System.clock - start + System.print(" 100 fibers x 100 yields = %(shared) operations in %(elapsed)s") + System.print(" Rate: %(shared/elapsed) ops/sec\n") + + // Test 5: Producer-consumer pattern + System.print("Test 5: Producer-Consumer pattern") + var queue = [] + var produced = 0 + var consumed = 0 + + var producer = Fiber.new { + for (i in 1..1000) { + queue.add(i) + produced = produced + 1 + if (queue.count > 10) Fiber.yield() + } + } + + var consumer = Fiber.new { + while (consumed < 1000) { + if (queue.count > 0) { + var item = queue.removeAt(0) + consumed = consumed + 1 + } else { + Fiber.yield() + } + } + } + + start = System.clock + while (!producer.isDone || !consumer.isDone) { + if (!producer.isDone) producer.call() + if (!consumer.isDone) consumer.call() + } + elapsed = System.clock - start + System.print(" Produced: %(produced), Consumed: %(consumed)") + System.print(" Time: %(elapsed)s") + System.print(" Rate: %(consumed/elapsed) items/sec\n") + + // Test 6: Deep call stack + System.print("Test 6: Deep recursion with yields") + var depth = 0 + var maxDepth = 1000 + var recursive + recursive = Fiber.new { + depth = depth + 1 + if (depth < maxDepth) { + Fiber.yield() + recursive.call() + } + } + + start = System.clock + while (!recursive.isDone) { + recursive.call() + } + elapsed = System.clock - start + System.print(" Recursion depth %(maxDepth): %(elapsed)s") + System.print(" Per level: %(elapsed/maxDepth * 1000) milliseconds\n") + + System.print("=== Analysis ===") + System.print("Based on these results, Wren fibers are:") + var yieldMicros = elapsed/iterations * 1000000 + if (yieldMicros < 1) { + System.print(" EXCELLENT - Sub-microsecond yields") + } else if (yieldMicros < 10) { + System.print(" GOOD - Single-digit microsecond yields") + } else if (yieldMicros < 50) { + System.print(" MODERATE - Tens of microseconds per yield") + } else { + System.print(" SLOW - Over 50 microseconds per yield") + System.print(" This suggests FFI overhead or implementation issues") + } + + Host.signalDone() + while(true) { Fiber.yield() } +} diff --git a/socket_diagnostic.wren b/socket_diagnostic.wren new file mode 100644 index 0000000..aaa88bc --- /dev/null +++ b/socket_diagnostic.wren @@ -0,0 +1,251 @@ +// socket_diagnostic.wren - Diagnose socket issues +import "socket" for Socket, SocketInstance + +foreign class Host { + foreign static signalDone() +} + +var mainFiber = Fiber.new { + System.print("=== Socket Diagnostic Test ===\n") + + // Test 1: Basic socket creation + System.print("Test 1: Socket creation") + var sock1 = SocketInstance.new() + if (sock1 && !sock1[0]) { + System.print(" ✓ Socket created successfully") + sock1[1].close() + } else { + System.print(" ✗ Failed: %(sock1 ? sock1[0] : "null result")") + } + + // Test 2: Bind to different ports + System.print("\nTest 2: Port binding") + var ports = [18000, 18001, 18002, 18003, 18004] + var boundSockets = [] + + for (port in ports) { + var result = SocketInstance.new() + if (result && !result[0]) { + var sock = result[1] + var bindResult = sock.bind("127.0.0.1", port) + if (bindResult && !bindResult[0]) { + System.print(" ✓ Bound to port %(port)") + boundSockets.add(sock) + } else { + System.print(" ✗ Failed to bind port %(port): %(bindResult ? bindResult[0] : "null")") + sock.close() + } + } + } + + // Clean up + for (sock in boundSockets) { + sock.close() + } + + // Test 3: Listen without bind (should fail) + System.print("\nTest 3: Listen without bind") + var result = SocketInstance.new() + if (result && !result[0]) { + var sock = result[1] + var listenResult = sock.listen(5) + if (listenResult && listenResult[0]) { + System.print(" ✓ Correctly failed: %(listenResult[0])") + } else { + System.print(" ✗ Unexpectedly succeeded") + } + sock.close() + } + + // Test 4: Full server setup + System.print("\nTest 4: Full server setup") + result = SocketInstance.new() + if (!result || result[0]) { + System.print(" ✗ Failed to create server socket") + } else { + var serverSock = result[1] + System.print(" ✓ Server socket created") + + var bindResult = serverSock.bind("0.0.0.0", 18080) + if (!bindResult || bindResult[0]) { + System.print(" ✗ Bind failed: %(bindResult ? bindResult[0] : "null")") + serverSock.close() + } else { + System.print(" ✓ Bound to 0.0.0.0:18080") + + var listenResult = serverSock.listen(10) + if (!listenResult || listenResult[0]) { + System.print(" ✗ Listen failed: %(listenResult ? listenResult[0] : "null")") + } else { + System.print(" ✓ Listening with backlog 10") + } + serverSock.close() + } + } + + // Test 5: Rapid open/close + System.print("\nTest 5: Rapid socket open/close") + var successCount = 0 + for (i in 1..100) { + var r = SocketInstance.new() + if (r && !r[0]) { + successCount = successCount + 1 + r[1].close() + } + } + System.print(" Created and closed %(successCount)/100 sockets") + + // Test 6: Connect to non-existent server (should fail quickly) + System.print("\nTest 6: Connect to non-existent server") + var startTime = System.clock + var connectResult = SocketInstance.connect("127.0.0.1", 19999) + var elapsed = System.clock - startTime + if (connectResult && connectResult[0]) { + System.print(" ✓ Correctly failed in %(elapsed)s: %(connectResult[0])") + } else { + System.print(" ✗ Unexpectedly succeeded") + if (connectResult && connectResult[1]) { + connectResult[1].close() + } + } + + // Test 7: Actual echo test with verbose output + System.print("\nTest 7: Simple echo test") + + // Create server + var serverResult = SocketInstance.new() + if (!serverResult || serverResult[0]) { + System.print(" ✗ Server socket creation failed: %(serverResult ? serverResult[0] : "null")") + } else { + var serverSock = serverResult[1] + System.print(" ✓ Server socket created") + + // Try multiple ports in case one is in use + var serverPort = 0 + var bindSuccess = false + for (port in [20000, 20001, 20002, 20003, 20004]) { + var bindResult = serverSock.bind("127.0.0.1", port) + if (bindResult && !bindResult[0]) { + serverPort = port + bindSuccess = true + System.print(" ✓ Server bound to port %(port)") + break + } + } + + if (!bindSuccess) { + System.print(" ✗ Could not bind to any port") + serverSock.close() + } else { + var listenResult = serverSock.listen(5) + if (!listenResult || listenResult[0]) { + System.print(" ✗ Listen failed: %(listenResult ? listenResult[0] : "null")") + serverSock.close() + } else { + System.print(" ✓ Server listening") + + // Server fiber + var serverDone = false + var serverFiber = Fiber.new { + System.print(" [Server] Waiting for connection...") + var attempts = 0 + while (attempts < 500) { + var acceptResult = serverSock.accept() + if (acceptResult && !acceptResult[0] && acceptResult[1]) { + System.print(" [Server] ✓ Accepted connection") + var clientSock = SocketInstance.fromFd(acceptResult[1]) + + // Read from client + var readAttempts = 0 + while (readAttempts < 100) { + var readResult = clientSock.read(256) + if (readResult && !readResult[0] && readResult[1] && readResult[1].count > 0) { + System.print(" [Server] ✓ Received: %(readResult[1])") + + // Echo back + var writeErr = clientSock.write(readResult[1]) + if (writeErr) { + System.print(" [Server] ✗ Write failed: %(writeErr)") + } else { + System.print(" [Server] ✓ Echoed back") + } + break + } + readAttempts = readAttempts + 1 + Fiber.yield() + } + + clientSock.close() + break + } + attempts = attempts + 1 + Fiber.yield() + } + if (attempts >= 500) { + System.print(" [Server] ✗ No connection after %(attempts) attempts") + } + serverDone = true + } + + // Client fiber + var clientDone = false + var clientFiber = Fiber.new { + // Wait a bit for server to be ready + for (i in 1..10) Fiber.yield() + + System.print(" [Client] Connecting to port %(serverPort)...") + var connectResult = SocketInstance.connect("127.0.0.1", serverPort) + if (!connectResult || connectResult[0]) { + System.print(" [Client] ✗ Connect failed: %(connectResult ? connectResult[0] : "null")") + } else { + var clientSock = connectResult[1] + System.print(" [Client] ✓ Connected") + + var message = "Hello, Server!" + var writeErr = clientSock.write(message) + if (writeErr) { + System.print(" [Client] ✗ Write failed: %(writeErr)") + } else { + System.print(" [Client] ✓ Sent: %(message)") + + // Read echo + var readAttempts = 0 + while (readAttempts < 100) { + var readResult = clientSock.read(256) + if (readResult && !readResult[0] && readResult[1] && readResult[1].count > 0) { + System.print(" [Client] ✓ Received echo: %(readResult[1])") + break + } + readAttempts = readAttempts + 1 + Fiber.yield() + } + } + + clientSock.close() + } + clientDone = true + } + + // Run both fibers + serverFiber.call() + clientFiber.call() + + var iterations = 0 + while ((!serverDone || !clientDone) && iterations < 1000) { + if (!serverDone) serverFiber.call() + if (!clientDone) clientFiber.call() + iterations = iterations + 1 + Fiber.yield() + } + + System.print(" Test completed after %(iterations) iterations") + serverSock.close() + } + } + } + + System.print("\n=== Diagnostic Complete ===") + Host.signalDone() + + while(true) { Fiber.yield() } +} diff --git a/socket_echo_debug.wren b/socket_echo_debug.wren new file mode 100644 index 0000000..79b5e13 --- /dev/null +++ b/socket_echo_debug.wren @@ -0,0 +1,260 @@ +// socket_echo_debug.wren - Debug why echo test fails +import "socket" for Socket, SocketInstance + +foreign class Host { + foreign static signalDone() +} + +var mainFiber = Fiber.new { + System.print("=== Debug Echo Test ===\n") + + // Create and setup server + System.print("1. Creating server socket...") + var serverResult = SocketInstance.new() + if (!serverResult || serverResult[0]) { + System.print(" ERROR: Failed to create server socket") + Host.signalDone() + while(true) { Fiber.yield() } + } + var serverSock = serverResult[1] + System.print(" ✓ Server socket created") + + System.print("\n2. Binding to port 30000...") + var bindResult = serverSock.bind("127.0.0.1", 30000) + if (bindResult[0]) { + System.print(" ERROR: Bind failed: %(bindResult[0])") + serverSock.close() + Host.signalDone() + while(true) { Fiber.yield() } + } + System.print(" ✓ Bound successfully") + + System.print("\n3. Starting listen...") + var listenResult = serverSock.listen(5) + if (listenResult[0]) { + System.print(" ERROR: Listen failed: %(listenResult[0])") + serverSock.close() + Host.signalDone() + while(true) { Fiber.yield() } + } + System.print(" ✓ Listening") + + var serverDone = false + var serverAccepted = false + var serverMessages = 0 + + // Server fiber - with detailed logging + var serverFiber = Fiber.new { + System.print("\n[SERVER] Starting accept loop...") + var acceptAttempts = 0 + var maxAttempts = 1000 + + while (acceptAttempts < maxAttempts && !serverAccepted) { + acceptAttempts = acceptAttempts + 1 + + if (acceptAttempts % 100 == 0) { + System.print("[SERVER] Accept attempt %(acceptAttempts)...") + } + + var acceptResult = serverSock.accept() + + if (acceptResult) { + if (acceptResult[0]) { + // Only print non-"would block" errors + var errStr = acceptResult[0] + var isWouldBlock = false + if (errStr.count > 5) { + // Check for common "would block" messages + if (errStr.indexOf("block") >= 0 || errStr.indexOf("Would") >= 0) { + isWouldBlock = true + } + } + if (!isWouldBlock) { + System.print("[SERVER] Accept error: %(acceptResult[0])") + } + } else if (acceptResult[1]) { + System.print("[SERVER] ✓ Connection accepted! fd=%(acceptResult[1])") + serverAccepted = true + + var clientSock = SocketInstance.fromFd(acceptResult[1]) + System.print("[SERVER] Created client socket wrapper") + + // Echo loop + var readAttempts = 0 + var maxReads = 500 + + System.print("[SERVER] Starting read loop...") + while (readAttempts < maxReads && serverMessages < 5) { + readAttempts = readAttempts + 1 + + if (readAttempts % 50 == 0) { + System.print("[SERVER] Read attempt %(readAttempts)...") + } + + var readResult = clientSock.read(1024) + + if (readResult) { + if (readResult[0]) { + var errStr = readResult[0] + var isWouldBlock = false + if (errStr && errStr.count > 5) { + if (errStr.indexOf("block") >= 0 || errStr.indexOf("Would") >= 0) { + isWouldBlock = true + } + } + if (!isWouldBlock) { + System.print("[SERVER] Read error: %(readResult[0])") + } + } else if (readResult[1] && readResult[1].count > 0) { + serverMessages = serverMessages + 1 + System.print("[SERVER] ✓ Received message #%(serverMessages): %(readResult[1].count) bytes") + + // Echo back + var writeErr = clientSock.write(readResult[1]) + if (writeErr) { + System.print("[SERVER] Write error: %(writeErr)") + } else { + System.print("[SERVER] ✓ Echoed back %(readResult[1].count) bytes") + } + } + } + + Fiber.yield() + } + + System.print("[SERVER] Read loop ended after %(readAttempts) attempts") + System.print("[SERVER] Messages processed: %(serverMessages)") + + clientSock.close() + System.print("[SERVER] Client socket closed") + break + } + } + + Fiber.yield() + } + + if (!serverAccepted) { + System.print("[SERVER] Never accepted connection after %(acceptAttempts) attempts") + } + + System.print("[SERVER] Done") + serverDone = true + } + + var clientDone = false + var clientMessages = 0 + + // Client fiber - with detailed logging + var clientFiber = Fiber.new { + System.print("\n[CLIENT] Waiting for server to be ready...") + for (i in 1..50) { + Fiber.yield() + } + + System.print("[CLIENT] Attempting to connect to 127.0.0.1:30000...") + var connectResult = SocketInstance.connect("127.0.0.1", 30000) + + if (!connectResult) { + System.print("[CLIENT] ERROR: Connect returned null") + clientDone = true + return + } + + if (connectResult[0]) { + System.print("[CLIENT] ERROR: Connect failed: %(connectResult[0])") + clientDone = true + return + } + + var clientSock = connectResult[1] + System.print("[CLIENT] ✓ Connected successfully") + + // Send 5 test messages + for (i in 1..5) { + var message = "Test message #%(i)" + System.print("[CLIENT] Sending: %(message)") + + var writeErr = clientSock.write(message) + if (writeErr) { + System.print("[CLIENT] Write error: %(writeErr)") + break + } else { + System.print("[CLIENT] ✓ Sent successfully") + clientMessages = clientMessages + 1 + + // Wait for echo + var gotEcho = false + var echoAttempts = 0 + + System.print("[CLIENT] Waiting for echo...") + while (echoAttempts < 100 && !gotEcho) { + echoAttempts = echoAttempts + 1 + + var readResult = clientSock.read(1024) + if (readResult) { + if (!readResult[0] && readResult[1] && readResult[1].count > 0) { + System.print("[CLIENT] ✓ Received echo: %(readResult[1])") + gotEcho = true + } + } + + if (echoAttempts % 20 == 0) { + System.print("[CLIENT] Still waiting for echo... (attempt %(echoAttempts))") + } + + Fiber.yield() + } + + if (!gotEcho) { + System.print("[CLIENT] WARNING: No echo received after %(echoAttempts) attempts") + } + } + + // Small delay between messages + for (j in 1..10) Fiber.yield() + } + + System.print("[CLIENT] Sent %(clientMessages) messages") + + clientSock.close() + System.print("[CLIENT] Socket closed") + System.print("[CLIENT] Done") + clientDone = true + } + + // Start both fibers + System.print("\n4. Starting server and client fibers...") + serverFiber.call() + clientFiber.call() + + // Run event loop with monitoring + var iterations = 0 + var maxIterations = 2000 + var lastPrint = 0 + + while ((!serverDone || !clientDone) && iterations < maxIterations) { + if (!serverDone) serverFiber.call() + if (!clientDone) clientFiber.call() + + iterations = iterations + 1 + + if (iterations - lastPrint >= 100) { + System.print("\n[MAIN] Iteration %(iterations): server=%(serverDone ? "done" : "running"), client=%(clientDone ? "done" : "running")") + lastPrint = iterations + } + + Fiber.yield() + } + + System.print("\n=== Test Complete ===") + System.print("Total iterations: %(iterations)") + System.print("Server accepted: %(serverAccepted)") + System.print("Server messages: %(serverMessages)") + System.print("Client messages: %(clientMessages)") + + serverSock.close() + + Host.signalDone() + while(true) { Fiber.yield() } +} diff --git a/socket_example.wren b/socket_example.wren index 54ede35..cc87cda 100644 --- a/socket_example.wren +++ b/socket_example.wren @@ -5,7 +5,6 @@ foreign class Host { foreign static signalDone() } -// Helper class for time-related functions. class Time { static sleep(ms) { var start = System.clock @@ -16,111 +15,176 @@ class Time { } var mainFiber = Fiber.new { - var serverFiber = Fiber.new { - System.print("Server: Starting up...") - var result = SocketInstance.new() - var err = result[0] - var serverSock = result[1] - if (err) { - System.print("Server Error creating socket: %(err)") - return - } - - result = serverSock.bind("127.0.0.1", 8080) - err = result[0] - var success = result[1] - if (err) { - System.print("Server Error binding: %(err)") - return - } - System.print("Server: Bound to 127.0.0.1:8080") - - result = serverSock.listen(5) - err = result[0] - success = result[1] - if (err) { - System.print("Server Error listening: %(err)") - return - } - System.print("Server: Listening for connections...") - - while (true) { - result = serverSock.accept() - err = result[0] - var clientSockFd = result[1] - if (err) { - System.print("Server Error accepting: %(err)") - continue - } - var clientSock = SocketInstance.new(clientSockFd) - System.print("Server: Accepted connection.") - - Fiber.new { - while(true){ - var result = clientSock.read(1024) - var err = result[0] - var data = result[1] - if (err) { - System.print("Server Error reading: %(err)") - return - } - System.print("Server received: %(data)") - - var response = "Hello from server!" - err = clientSock.write(response) - if (err) { - System.print("Server Error writing: %(err)") - } - System.print("Server sent response.") - } - }.call() - } - } - - var clientFiber = Fiber.new { - // Give the server a moment to start up. - Time.sleep(100) - - System.print("Client: Connecting...") - var result = SocketInstance.connect("127.0.0.1", 8080) - var err = result[0] - var clientSock = result[1] - if (err) { - System.print("Client Error connecting: %(err)") - Host.signalDone() - return - } - System.print("Client: Connected.") - - var message = "Hello from client!" - err = clientSock.write(message) - if (err) { - System.print("Client Error writing: %(err)") - Host.signalDone() - return - } - System.print("Client: Sent message.") - - result = clientSock.read(1024) - err = result[0] - var response = result[1] - if (err) { - System.print("Client Error reading: %(err)") - Host.signalDone() - return - } - System.print("Client received: %(response)") - - // All done. + System.print("=== Simple Socket Test ===") + + // Test 1: Create a server socket + System.print("\n1. Creating server socket...") + var serverResult = SocketInstance.new() + if (!serverResult) { + System.print("Failed to create server socket") Host.signalDone() + return } - - serverFiber.call() - clientFiber.call() - - // Keep the main fiber alive until Host.signalDone() is called. - while(true) { - Fiber.yield() + + var serverErr = serverResult[0] + var serverSock = serverResult[1] + + if (serverErr) { + System.print("Server socket error: %(serverErr)") + Host.signalDone() + return } + + System.print("Server socket created successfully") + + // Test 2: Bind the server socket + System.print("\n2. Binding server socket to port 11000...") + var bindResult = serverSock.bind("127.0.0.1", 11000) + var bindErr = bindResult[0] + var bindSuccess = bindResult[1] + + if (bindErr) { + System.print("Bind error: %(bindErr)") + serverSock.close() + Host.signalDone() + return + } + + System.print("Server socket bound successfully") + + // Test 3: Listen on the server socket + System.print("\n3. Starting to listen...") + var listenResult = serverSock.listen(5) + var listenErr = listenResult[0] + var listenSuccess = listenResult[1] + + if (listenErr) { + System.print("Listen error: %(listenErr)") + serverSock.close() + Host.signalDone() + return + } + + System.print("Server listening successfully") + + // Test 4: Create and connect a client socket + System.print("\n4. Creating client socket and connecting...") + var clientResult = SocketInstance.connect("127.0.0.1", 11000) + if (!clientResult) { + System.print("Failed to create client socket") + serverSock.close() + Host.signalDone() + return + } + + var clientErr = clientResult[0] + var clientSock = clientResult[1] + + if (clientErr) { + System.print("Client connection error: %(clientErr)") + serverSock.close() + Host.signalDone() + return + } + + System.print("Client connected successfully") + + // Test 5: Accept connection on server + System.print("\n5. Accepting connection on server...") + var acceptAttempts = 0 + var accepted = false + var acceptedSock = null + + while (acceptAttempts < 100 && !accepted) { + var acceptResult = serverSock.accept() + var acceptErr = acceptResult[0] + var acceptedFd = acceptResult[1] + + if (!acceptErr && acceptedFd) { + acceptedSock = SocketInstance.fromFd(acceptedFd) + accepted = true + System.print("Connection accepted successfully") + } else { + acceptAttempts = acceptAttempts + 1 + Fiber.yield() + } + } + + if (!accepted) { + System.print("Failed to accept connection after %(acceptAttempts) attempts") + clientSock.close() + serverSock.close() + Host.signalDone() + return + } + + // Test 6: Send data from client to server + System.print("\n6. Sending data from client to server...") + var testMessage = "Hello from client!" + var writeErr = clientSock.write(testMessage) + + if (writeErr) { + System.print("Client write error: %(writeErr)") + } else { + System.print("Client sent: %(testMessage)") + } + + // Test 7: Read data on server side + System.print("\n7. Reading data on server side...") + var readAttempts = 0 + var dataReceived = false + + while (readAttempts < 100 && !dataReceived) { + var readResult = acceptedSock.read(1024) + var readErr = readResult[0] + var readData = readResult[1] + + if (!readErr && readData && readData.count > 0) { + System.print("Server received: %(readData)") + dataReceived = true + + // Echo back + var echoErr = acceptedSock.write(readData) + if (echoErr) { + System.print("Server echo error: %(echoErr)") + } else { + System.print("Server echoed data back") + } + } else { + readAttempts = readAttempts + 1 + Fiber.yield() + } + } + + if (!dataReceived) { + System.print("No data received after %(readAttempts) attempts") + } + + // Test 8: Read echo on client side + System.print("\n8. Reading echo on client side...") + readAttempts = 0 + var echoReceived = false + + while (readAttempts < 100 && !echoReceived) { + var readResult = clientSock.read(1024) + var readErr = readResult[0] + var readData = readResult[1] + + if (!readErr && readData && readData.count > 0) { + System.print("Client received echo: %(readData)") + echoReceived = true + } else { + readAttempts = readAttempts + 1 + Fiber.yield() + } + } + + // Clean up + System.print("\n9. Cleaning up...") + acceptedSock.close() + clientSock.close() + serverSock.close() + + System.print("\n=== All tests completed successfully! ===") + Host.signalDone() } - diff --git a/socket_final_benchmark.wren b/socket_final_benchmark.wren new file mode 100644 index 0000000..bdaee5d --- /dev/null +++ b/socket_final_benchmark.wren @@ -0,0 +1,511 @@ +// socket_performance_fixed.wren - Fixed socket performance test +import "socket" for Socket, SocketInstance + +foreign class Host { + foreign static signalDone() +} + +class SocketPerformance { + static findFreePort(startPort) { + for (port in startPort...(startPort + 100)) { + var result = SocketInstance.new() + if (result && !result[0]) { + var sock = result[1] + var bindResult = sock.bind("127.0.0.1", port) + if (bindResult && !bindResult[0]) { + sock.close() + return port + } + sock.close() + } + } + return 0 + } + + static runEchoTest() { + System.print("\n=== Echo Performance Test ===") + + // Find a free port + var port = findFreePort(25000) + if (port == 0) { + System.print("Could not find free port") + return + } + System.print("Using port %(port)") + + // Create server + var serverResult = SocketInstance.new() + if (!serverResult || serverResult[0]) { + System.print("Failed to create server") + return + } + var serverSock = serverResult[1] + + // Bind and listen + var bindResult = serverSock.bind("127.0.0.1", port) + if (bindResult[0]) { + System.print("Bind failed: %(bindResult[0])") + serverSock.close() + return + } + + var listenResult = serverSock.listen(10) + if (listenResult[0]) { + System.print("Listen failed: %(listenResult[0])") + serverSock.close() + return + } + + var messageCount = 1000 + var messageSize = 100 + var message = "X" * messageSize + + var serverDone = false + var serverMessages = 0 + var serverBytes = 0 + + // Server fiber + var serverFiber = Fiber.new { + System.print(" Server waiting for connection...") + var acceptAttempts = 0 + var acceptResult = null + + // Try to accept with timeout + while (acceptAttempts < 1000) { + acceptAttempts = acceptAttempts + 1 + acceptResult = serverSock.accept() + + if (!acceptResult[0] && acceptResult[1]) { + System.print(" Server accepted connection after %(acceptAttempts) attempts") + break + } + + // Log progress every 100 attempts + if (acceptAttempts % 100 == 0) { + System.print(" Server still waiting... (attempt %(acceptAttempts))") + } + + Fiber.yield() + } + + if (acceptResult && !acceptResult[0] && acceptResult[1]) { + var clientSock = SocketInstance.fromFd(acceptResult[1]) + System.print(" Server processing messages...") + + while (serverMessages < messageCount) { + var readResult = clientSock.read(4096) + if (!readResult[0] && readResult[1] && readResult[1].count > 0) { + serverBytes = serverBytes + readResult[1].count + serverMessages = serverMessages + 1 + + // Echo back + clientSock.write(readResult[1]) + + // Log progress + if (serverMessages % 100 == 0) { + System.print(" Server processed %(serverMessages) messages") + } + } + + if (serverMessages % 10 == 0) { + Fiber.yield() + } + } + + clientSock.close() + } else { + System.print(" Server failed to accept connection after %(acceptAttempts) attempts") + } + serverDone = true + } + + var clientDone = false + var clientMessages = 0 + var clientBytes = 0 + + // Client fiber + var clientFiber = Fiber.new { + // Let server start - INCREASED WAIT TIME + System.print(" Waiting for server to start...") + for (i in 1..50) Fiber.yield() // Changed from 10 to 50 + + System.print(" Connecting...") + var connectResult = SocketInstance.connect("127.0.0.1", port) + if (connectResult[0]) { + System.print("Connect failed: %(connectResult[0])") + clientDone = true + return + } + var clientSock = connectResult[1] + System.print(" Connected successfully") + + var startTime = System.clock + + // Send messages and read echoes + for (i in 1..messageCount) { + var writeErr = clientSock.write(message) + if (!writeErr) { + clientMessages = clientMessages + 1 + + // Read echo - be more aggressive + var gotEcho = false + for (attempt in 1..20) { // Reduced from 100 - fail faster + var readResult = clientSock.read(4096) + if (!readResult[0] && readResult[1] && readResult[1].count > 0) { + clientBytes = clientBytes + readResult[1].count + gotEcho = true + break + } + // Only yield every few attempts to be more responsive + if (attempt % 5 == 0) Fiber.yield() + } + + if (!gotEcho) { + System.print(" No echo for message %(i) - stopping") + break + } + } else { + System.print(" Write error at message %(i): %(writeErr)") + break + } + + // Yield less frequently for better throughput + if (i % 50 == 0) { + Fiber.yield() + if (i % 200 == 0) { + System.print(" Client sent %(i) messages") + } + } + } + + var elapsed = System.clock - startTime + + System.print("\nClient Statistics:") + System.print(" Messages sent: %(clientMessages)") + System.print(" Bytes received: %(clientBytes)") + System.print(" Time: %(elapsed)s") + System.print(" Throughput: %(clientMessages/elapsed) msgs/sec") + System.print(" Bandwidth: %((clientBytes/elapsed/1024).floor) KB/sec") + + clientSock.close() + clientDone = true + } + + // Run both fibers + serverFiber.call() + clientFiber.call() + + var iterations = 0 + var maxIterations = 50000 // Increased from 10000 + while ((!serverDone || !clientDone) && iterations < maxIterations) { + if (!serverDone) serverFiber.call() + if (!clientDone) clientFiber.call() + iterations = iterations + 1 + + // Don't yield here - let the fibers control yielding + } + + System.print("\nServer Statistics:") + System.print(" Messages echoed: %(serverMessages)") + System.print(" Bytes processed: %(serverBytes)") + System.print(" Event loop iterations: %(iterations)") + + serverSock.close() + } + + static runConcurrentTest() { + System.print("\n=== Concurrent Connections Test ===") + + var port = findFreePort(26000) + if (port == 0) { + System.print("Could not find free port") + return + } + System.print("Using port %(port)") + + var serverResult = SocketInstance.new() + if (!serverResult || serverResult[0]) return + var serverSock = serverResult[1] + + serverSock.bind("127.0.0.1", port) + serverSock.listen(50) + + var connectionCount = 20 + var connectionsAccepted = 0 + var messagesSent = 0 + + // Server fiber - handles all connections + var serverDone = false + var serverFiber = Fiber.new { + var handlers = [] + var attempts = 0 + + while (connectionsAccepted < connectionCount && attempts < 2000) { + var acceptResult = serverSock.accept() + if (!acceptResult[0] && acceptResult[1]) { + connectionsAccepted = connectionsAccepted + 1 + var clientSock = SocketInstance.fromFd(acceptResult[1]) + + // Handler for this connection + var handler = Fiber.new { + var msg = "Hello from server to connection %(connectionsAccepted)!" + clientSock.write(msg) + messagesSent = messagesSent + 1 + + // Read client response + for (i in 1..20) { + var readResult = clientSock.read(256) + if (!readResult[0] && readResult[1] && readResult[1].count > 0) { + break + } + Fiber.yield() + } + + clientSock.close() + } + + handlers.add(handler) + handler.call() + } + + // Process active handlers + var active = [] + for (h in handlers) { + if (!h.isDone) { + h.call() + active.add(h) + } + } + handlers = active + + attempts = attempts + 1 + Fiber.yield() + } + + serverDone = true + } + + // Create client fibers + var clientFibers = [] + var clientsConnected = 0 + var messagesReceived = 0 + + var startTime = System.clock + + for (i in 1..connectionCount) { + var clientId = i + var clientFiber = Fiber.new { + // Stagger connections slightly + for (j in 1..clientId) Fiber.yield() + + var connectResult = SocketInstance.connect("127.0.0.1", port) + if (!connectResult[0]) { + clientsConnected = clientsConnected + 1 + var sock = connectResult[1] + + // Read server message + for (attempt in 1..50) { + var readResult = sock.read(256) + if (!readResult[0] && readResult[1] && readResult[1].count > 0) { + messagesReceived = messagesReceived + 1 + // Send response + sock.write("Client %(clientId) received: %(readResult[1])") + break + } + Fiber.yield() + } + + sock.close() + } + } + clientFibers.add(clientFiber) + } + + // Start all fibers + serverFiber.call() + for (cf in clientFibers) cf.call() + + // Run event loop + var iterations = 0 + while (iterations < 5000) { + if (!serverDone) serverFiber.call() + + var active = [] + for (cf in clientFibers) { + if (!cf.isDone) { + cf.call() + active.add(cf) + } + } + clientFibers = active + + if (serverDone && clientFibers.count == 0) break + + iterations = iterations + 1 + Fiber.yield() + } + + var elapsed = System.clock - startTime + + System.print("\nResults:") + System.print(" Connections attempted: %(connectionCount)") + System.print(" Clients connected: %(clientsConnected)") + System.print(" Connections accepted: %(connectionsAccepted)") + System.print(" Messages sent by server: %(messagesSent)") + System.print(" Messages received by clients: %(messagesReceived)") + System.print(" Time: %(elapsed)s") + System.print(" Connection rate: %(clientsConnected/elapsed) conn/sec") + System.print(" Event loop iterations: %(iterations)") + + serverSock.close() + } + + static runLatencyTest() { + System.print("\n=== Latency Test (ping-pong) ===") + + var port = findFreePort(27000) + if (port == 0) { + System.print("Could not find free port") + return + } + System.print("Using port %(port)") + + var serverResult = SocketInstance.new() + if (!serverResult || serverResult[0]) { + System.print("Failed to create server socket") + return + } + var serverSock = serverResult[1] + + var bindResult = serverSock.bind("127.0.0.1", port) + if (bindResult[0]) { + System.print("Bind failed: %(bindResult[0])") + serverSock.close() + return + } + + var listenResult = serverSock.listen(1) + if (listenResult[0]) { + System.print("Listen failed: %(listenResult[0])") + serverSock.close() + return + } + + var pingCount = 100 + var serverDone = false + + // Server: respond to pings with pongs + var serverFiber = Fiber.new { + var acceptResult = serverSock.accept() + if (!acceptResult[0] && acceptResult[1]) { + var clientSock = SocketInstance.fromFd(acceptResult[1]) + + for (i in 1..pingCount) { + // Wait for ping + var gotPing = false + for (attempt in 1..100) { + var readResult = clientSock.read(256) + if (!readResult[0] && readResult[1] && readResult[1].count > 0) { + // Send pong immediately + clientSock.write("pong") + gotPing = true + break + } + Fiber.yield() + } + if (!gotPing) break + } + + clientSock.close() + } + serverDone = true + } + + var clientDone = false + var latencies = [] + + // Client: send pings and measure round-trip time + var clientFiber = Fiber.new { + for (i in 1..10) Fiber.yield() + + var connectResult = SocketInstance.connect("127.0.0.1", port) + if (connectResult[0]) { + clientDone = true + return + } + var clientSock = connectResult[1] + + for (i in 1..pingCount) { + var pingStart = System.clock + + // Send ping + clientSock.write("ping") + + // Wait for pong + var gotPong = false + for (attempt in 1..100) { + var readResult = clientSock.read(256) + if (!readResult[0] && readResult[1] && readResult[1].count > 0) { + var latency = (System.clock - pingStart) * 1000 // Convert to ms + latencies.add(latency) + gotPong = true + break + } + Fiber.yield() + } + + if (!gotPong) break + } + + clientSock.close() + clientDone = true + } + + // Run test + serverFiber.call() + clientFiber.call() + + while (!serverDone || !clientDone) { + if (!serverDone) serverFiber.call() + if (!clientDone) clientFiber.call() + Fiber.yield() + } + + // Calculate statistics + if (latencies.count > 0) { + var sum = 0 + var min = latencies[0] + var max = latencies[0] + + for (lat in latencies) { + sum = sum + lat + if (lat < min) min = lat + if (lat > max) max = lat + } + + var avg = sum / latencies.count + + System.print("\nLatency Statistics (%(latencies.count) samples):") + System.print(" Average: %(avg) ms") + System.print(" Min: %(min) ms") + System.print(" Max: %(max) ms") + System.print(" Ping rate: %(1000/avg) pings/sec possible") + } + + serverSock.close() + } +} + +var mainFiber = Fiber.new { + System.print("=== Socket Performance Test Suite ===") + System.print("With fiber performance: 2.6M yields/sec (0.38μs/yield)") + + SocketPerformance.runEchoTest() + SocketPerformance.runConcurrentTest() + SocketPerformance.runLatencyTest() + + System.print("\n=== All Tests Complete ===") + Host.signalDone() + + while(true) { Fiber.yield() } +} diff --git a/socket_performance.wren b/socket_performance.wren new file mode 100644 index 0000000..58b2038 --- /dev/null +++ b/socket_performance.wren @@ -0,0 +1,450 @@ +// socket_performance_fixed.wren - Fixed socket performance test +import "socket" for Socket, SocketInstance + +foreign class Host { + foreign static signalDone() +} + +class SocketPerformance { + static findFreePort(startPort) { + for (port in startPort...(startPort + 100)) { + var result = SocketInstance.new() + if (result && !result[0]) { + var sock = result[1] + var bindResult = sock.bind("127.0.0.1", port) + if (bindResult && !bindResult[0]) { + sock.close() + return port + } + sock.close() + } + } + return 0 + } + + static runEchoTest() { + System.print("\n=== Echo Performance Test ===") + + // Find a free port + var port = findFreePort(25000) + if (port == 0) { + System.print("Could not find free port") + return + } + System.print("Using port %(port)") + + // Create server + var serverResult = SocketInstance.new() + if (!serverResult || serverResult[0]) { + System.print("Failed to create server") + return + } + var serverSock = serverResult[1] + + // Bind and listen + var bindResult = serverSock.bind("127.0.0.1", port) + if (bindResult[0]) { + System.print("Bind failed: %(bindResult[0])") + serverSock.close() + return + } + + var listenResult = serverSock.listen(10) + if (listenResult[0]) { + System.print("Listen failed: %(listenResult[0])") + serverSock.close() + return + } + + var messageCount = 1000 + var messageSize = 100 + var message = "X" * messageSize + + var serverDone = false + var serverMessages = 0 + var serverBytes = 0 + + // Server fiber + var serverFiber = Fiber.new { + var acceptResult = serverSock.accept() + if (!acceptResult[0] && acceptResult[1]) { + var clientSock = SocketInstance.fromFd(acceptResult[1]) + + while (serverMessages < messageCount) { + var readResult = clientSock.read(4096) + if (!readResult[0] && readResult[1] && readResult[1].count > 0) { + serverBytes = serverBytes + readResult[1].count + serverMessages = serverMessages + 1 + + // Echo back + clientSock.write(readResult[1]) + } + + if (serverMessages % 10 == 0) { + Fiber.yield() + } + } + + clientSock.close() + } + serverDone = true + } + + var clientDone = false + var clientMessages = 0 + var clientBytes = 0 + + // Client fiber + var clientFiber = Fiber.new { + // Let server start + for (i in 1..10) Fiber.yield() + + var connectResult = SocketInstance.connect("127.0.0.1", port) + if (connectResult[0]) { + System.print("Connect failed: %(connectResult[0])") + clientDone = true + return + } + var clientSock = connectResult[1] + + var startTime = System.clock + + // Send messages and read echoes + for (i in 1..messageCount) { + var writeErr = clientSock.write(message) + if (!writeErr) { + clientMessages = clientMessages + 1 + + // Read echo (with timeout) + var gotEcho = false + for (attempt in 1..50) { + var readResult = clientSock.read(4096) + if (!readResult[0] && readResult[1] && readResult[1].count > 0) { + clientBytes = clientBytes + readResult[1].count + gotEcho = true + break + } + if (attempt % 10 == 0) Fiber.yield() + } + + if (!gotEcho && i > 10) { + System.print("No echo received for message %(i)") + break + } + } + + if (i % 10 == 0) Fiber.yield() + } + + var elapsed = System.clock - startTime + + System.print("\nClient Statistics:") + System.print(" Messages sent: %(clientMessages)") + System.print(" Bytes received: %(clientBytes)") + System.print(" Time: %(elapsed)s") + System.print(" Throughput: %(clientMessages/elapsed) msgs/sec") + System.print(" Bandwidth: %((clientBytes/elapsed/1024).floor) KB/sec") + + clientSock.close() + clientDone = true + } + + // Run both fibers + serverFiber.call() + clientFiber.call() + + var iterations = 0 + var maxIterations = 10000 + while ((!serverDone || !clientDone) && iterations < maxIterations) { + if (!serverDone) serverFiber.call() + if (!clientDone) clientFiber.call() + iterations = iterations + 1 + Fiber.yield() + } + + System.print("\nServer Statistics:") + System.print(" Messages echoed: %(serverMessages)") + System.print(" Bytes processed: %(serverBytes)") + System.print(" Event loop iterations: %(iterations)") + + serverSock.close() + } + + static runConcurrentTest() { + System.print("\n=== Concurrent Connections Test ===") + + var port = findFreePort(26000) + if (port == 0) { + System.print("Could not find free port") + return + } + System.print("Using port %(port)") + + var serverResult = SocketInstance.new() + if (!serverResult || serverResult[0]) return + var serverSock = serverResult[1] + + serverSock.bind("127.0.0.1", port) + serverSock.listen(50) + + var connectionCount = 20 + var connectionsAccepted = 0 + var messagesSent = 0 + + // Server fiber - handles all connections + var serverDone = false + var serverFiber = Fiber.new { + var handlers = [] + var attempts = 0 + + while (connectionsAccepted < connectionCount && attempts < 2000) { + var acceptResult = serverSock.accept() + if (!acceptResult[0] && acceptResult[1]) { + connectionsAccepted = connectionsAccepted + 1 + var clientSock = SocketInstance.fromFd(acceptResult[1]) + + // Handler for this connection + var handler = Fiber.new { + var msg = "Hello from server to connection %(connectionsAccepted)!" + clientSock.write(msg) + messagesSent = messagesSent + 1 + + // Read client response + for (i in 1..20) { + var readResult = clientSock.read(256) + if (!readResult[0] && readResult[1] && readResult[1].count > 0) { + break + } + Fiber.yield() + } + + clientSock.close() + } + + handlers.add(handler) + handler.call() + } + + // Process active handlers + var active = [] + for (h in handlers) { + if (!h.isDone) { + h.call() + active.add(h) + } + } + handlers = active + + attempts = attempts + 1 + Fiber.yield() + } + + serverDone = true + } + + // Create client fibers + var clientFibers = [] + var clientsConnected = 0 + var messagesReceived = 0 + + var startTime = System.clock + + for (i in 1..connectionCount) { + var clientId = i + var clientFiber = Fiber.new { + // Stagger connections slightly + for (j in 1..clientId) Fiber.yield() + + var connectResult = SocketInstance.connect("127.0.0.1", port) + if (!connectResult[0]) { + clientsConnected = clientsConnected + 1 + var sock = connectResult[1] + + // Read server message + for (attempt in 1..50) { + var readResult = sock.read(256) + if (!readResult[0] && readResult[1] && readResult[1].count > 0) { + messagesReceived = messagesReceived + 1 + // Send response + sock.write("Client %(clientId) received: %(readResult[1])") + break + } + Fiber.yield() + } + + sock.close() + } + } + clientFibers.add(clientFiber) + } + + // Start all fibers + serverFiber.call() + for (cf in clientFibers) cf.call() + + // Run event loop + var iterations = 0 + while (iterations < 5000) { + if (!serverDone) serverFiber.call() + + var active = [] + for (cf in clientFibers) { + if (!cf.isDone) { + cf.call() + active.add(cf) + } + } + clientFibers = active + + if (serverDone && clientFibers.count == 0) break + + iterations = iterations + 1 + Fiber.yield() + } + + var elapsed = System.clock - startTime + + System.print("\nResults:") + System.print(" Connections attempted: %(connectionCount)") + System.print(" Clients connected: %(clientsConnected)") + System.print(" Connections accepted: %(connectionsAccepted)") + System.print(" Messages sent by server: %(messagesSent)") + System.print(" Messages received by clients: %(messagesReceived)") + System.print(" Time: %(elapsed)s") + System.print(" Connection rate: %(clientsConnected/elapsed) conn/sec") + System.print(" Event loop iterations: %(iterations)") + + serverSock.close() + } + + static runLatencyTest() { + System.print("\n=== Latency Test (ping-pong) ===") + + var port = findFreePort(27000) + if (port == 0) return + + var serverResult = SocketInstance.new() + if (!serverResult || serverResult[0]) return + var serverSock = serverResult[1] + + serverSock.bind("127.0.0.1", port) + serverSock.listen(1) + + var pingCount = 100 + var serverDone = false + + // Server: respond to pings with pongs + var serverFiber = Fiber.new { + var acceptResult = serverSock.accept() + if (!acceptResult[0] && acceptResult[1]) { + var clientSock = SocketInstance.fromFd(acceptResult[1]) + + for (i in 1..pingCount) { + // Wait for ping + var gotPing = false + for (attempt in 1..100) { + var readResult = clientSock.read(256) + if (!readResult[0] && readResult[1] && readResult[1].count > 0) { + // Send pong immediately + clientSock.write("pong") + gotPing = true + break + } + Fiber.yield() + } + if (!gotPing) break + } + + clientSock.close() + } + serverDone = true + } + + var clientDone = false + var latencies = [] + + // Client: send pings and measure round-trip time + var clientFiber = Fiber.new { + for (i in 1..10) Fiber.yield() + + var connectResult = SocketInstance.connect("127.0.0.1", port) + if (connectResult[0]) { + clientDone = true + return + } + var clientSock = connectResult[1] + + for (i in 1..pingCount) { + var pingStart = System.clock + + // Send ping + clientSock.write("ping") + + // Wait for pong + var gotPong = false + for (attempt in 1..100) { + var readResult = clientSock.read(256) + if (!readResult[0] && readResult[1] && readResult[1].count > 0) { + var latency = (System.clock - pingStart) * 1000 // Convert to ms + latencies.add(latency) + gotPong = true + break + } + Fiber.yield() + } + + if (!gotPong) break + } + + clientSock.close() + clientDone = true + } + + // Run test + serverFiber.call() + clientFiber.call() + + while (!serverDone || !clientDone) { + if (!serverDone) serverFiber.call() + if (!clientDone) clientFiber.call() + Fiber.yield() + } + + // Calculate statistics + if (latencies.count > 0) { + var sum = 0 + var min = latencies[0] + var max = latencies[0] + + for (lat in latencies) { + sum = sum + lat + if (lat < min) min = lat + if (lat > max) max = lat + } + + var avg = sum / latencies.count + + System.print("\nLatency Statistics (%(latencies.count) samples):") + System.print(" Average: %(avg) ms") + System.print(" Min: %(min) ms") + System.print(" Max: %(max) ms") + System.print(" Ping rate: %(1000/avg) pings/sec possible") + } + + serverSock.close() + } +} + +var mainFiber = Fiber.new { + System.print("=== Socket Performance Test Suite ===") + System.print("With fiber performance: 2.6M yields/sec (0.38μs/yield)") + + SocketPerformance.runEchoTest() + SocketPerformance.runConcurrentTest() + SocketPerformance.runLatencyTest() + + System.print("\n=== All Tests Complete ===") + Host.signalDone() + + while(true) { Fiber.yield() } +} diff --git a/socket_test.wren b/socket_test.wren new file mode 100644 index 0000000..16831d2 --- /dev/null +++ b/socket_test.wren @@ -0,0 +1,88 @@ +// socket_test_simple.wren +import "socket" for Socket, SocketInstance + +foreign class Host { + foreign static signalDone() +} + +var mainFiber = Fiber.new { + System.print("Testing socket creation...") + + // Test creating a socket using the high-level API + System.print("Creating socket via high-level API...") + var result = SocketInstance.new() + + if (result) { + System.print("Result received: %(result)") + + if (result.count != 2) { + System.print("Unexpected result format: expected [error, socket], got %(result)") + Host.signalDone() + while(true) { Fiber.yield() } // Keep fiber alive + return + } + + var err = result[0] + var sock = result[1] + + if (err) { + System.print("Error creating socket: %(err)") + } else if (!sock) { + System.print("Socket is null despite no error") + } else { + System.print("Successfully created socket!") + System.print("Socket instance: %(sock)") + + // Test binding + System.print("\nTesting bind...") + var bindResult = sock.bind("127.0.0.1", 12345) + System.print("Bind result: %(bindResult)") + + if (bindResult && bindResult.count == 2) { + if (bindResult[0]) { + System.print("Bind error: %(bindResult[0])") + } else { + System.print("Successfully bound to port 12345") + + // Test listen + System.print("\nTesting listen...") + var listenResult = sock.listen(5) + System.print("Listen result: %(listenResult)") + + if (listenResult && listenResult.count == 2) { + if (listenResult[0]) { + System.print("Listen error: %(listenResult[0])") + } else { + System.print("Successfully listening") + } + } else { + System.print("Unexpected listen result format") + } + } + } else { + System.print("Unexpected bind result format") + } + + // Close the socket + System.print("\nClosing socket...") + var closeErr = sock.close() + if (closeErr && closeErr != "Close operation did not complete") { + System.print("Close error: %(closeErr)") + } else if (closeErr == "Close operation did not complete") { + System.print("Close operation timed out") + } else { + System.print("Socket closed successfully") + } + } + } else { + System.print("Result was null") + } + + System.print("\nTest complete") + Host.signalDone() + + // Keep the fiber alive to prevent "Cannot call root fiber" error + while(true) { + Fiber.yield() + } +} diff --git a/string_backend.c b/string_backend.c index 5beac33..4bad45a 100644 --- a/string_backend.c +++ b/string_backend.c @@ -3,6 +3,8 @@ #include #include #include +#include +#include // Helper to validate that the value in a slot is a string. static bool validateString(WrenVM* vm, int slot, const char* name) { @@ -12,6 +14,14 @@ static bool validateString(WrenVM* vm, int slot, const char* name) { return false; } +// Helper to validate that the value in a slot is a number. +static bool validateNumber(WrenVM* vm, int slot, const char* name) { + if (wrenGetSlotType(vm, slot) == WREN_TYPE_NUM) return true; + wrenSetSlotString(vm, 0, "Argument must be a number."); + wrenAbortFiber(vm, 0); + return false; +} + // Implements String.endsWith(_). void stringEndsWith(WrenVM* vm) { if (!validateString(vm, 1, "Suffix")) return; @@ -55,22 +65,18 @@ void stringReplace(WrenVM* vm) { const char* to = wrenGetSlotBytes(vm, 2, &toLen); if (fromLen == 0) { - wrenSetSlotString(vm, 0, haystack); // Nothing to replace. + wrenSetSlotString(vm, 0, haystack); return; } - // Allocate a buffer for the result. This is a rough estimate. - // A more robust implementation would calculate the exact size or reallocate. size_t resultCapacity = haystackLen * (toLen > fromLen ? toLen / fromLen + 1 : 1) + 1; char* result = (char*)malloc(resultCapacity); if (!result) { - // Handle allocation failure wrenSetSlotString(vm, 0, "Memory allocation failed."); wrenAbortFiber(vm, 0); return; } - char* dest = result; const char* p = haystack; const char* end = haystack + haystackLen; @@ -111,7 +117,7 @@ void stringSplit(WrenVM* vm) { return; } - wrenSetSlotNewList(vm, 0); // Create the list to return. + wrenSetSlotNewList(vm, 0); const char* p = haystack; const char* end = haystack + haystackLen; @@ -128,24 +134,862 @@ void stringSplit(WrenVM* vm) { } } - // If the string ends with the delimiter, add an empty string. if (haystackLen > 0 && (haystackLen >= delimLen) && - strcmp(haystack + haystackLen - delimLen, delim) == 0) { + memcmp(haystack + haystackLen - delimLen, delim, delimLen) == 0) { wrenSetSlotBytes(vm, 1, "", 0); wrenInsertInList(vm, 0, -1, 1); } } +// Implements String.toUpper(). +void stringToUpper(WrenVM* vm) { + int len; + const char* str = wrenGetSlotBytes(vm, 0, &len); + + char* result = (char*)malloc(len + 1); + if (!result) { + wrenSetSlotString(vm, 0, "Memory allocation failed."); + wrenAbortFiber(vm, 0); + return; + } + + for (int i = 0; i < len; i++) { + result[i] = toupper((unsigned char)str[i]); + } + result[len] = '\0'; + + wrenSetSlotString(vm, 0, result); + free(result); +} + +// Implements String.toLower(). +void stringToLower(WrenVM* vm) { + int len; + const char* str = wrenGetSlotBytes(vm, 0, &len); + + char* result = (char*)malloc(len + 1); + if (!result) { + wrenSetSlotString(vm, 0, "Memory allocation failed."); + wrenAbortFiber(vm, 0); + return; + } + + for (int i = 0; i < len; i++) { + result[i] = tolower((unsigned char)str[i]); + } + result[len] = '\0'; + + wrenSetSlotString(vm, 0, result); + free(result); +} + +// Implements String.trim(). +void stringTrim(WrenVM* vm) { + int len; + const char* str = wrenGetSlotBytes(vm, 0, &len); + + int start = 0; + int end = len - 1; + + while (start < len && isspace((unsigned char)str[start])) start++; + while (end >= 0 && isspace((unsigned char)str[end])) end--; + + if (start > end) { + wrenSetSlotString(vm, 0, ""); + return; + } + + wrenSetSlotBytes(vm, 0, str + start, end - start + 1); +} + +// Implements String.trimStart(). +void stringTrimStart(WrenVM* vm) { + int len; + const char* str = wrenGetSlotBytes(vm, 0, &len); + + int start = 0; + while (start < len && isspace((unsigned char)str[start])) start++; + + wrenSetSlotBytes(vm, 0, str + start, len - start); +} + +// Implements String.trimEnd(). +void stringTrimEnd(WrenVM* vm) { + int len; + const char* str = wrenGetSlotBytes(vm, 0, &len); + + int end = len - 1; + while (end >= 0 && isspace((unsigned char)str[end])) end--; + + wrenSetSlotBytes(vm, 0, str, end + 1); +} + +// Implements String.count(_). +void stringCount(WrenVM* vm) { + if (!validateString(vm, 1, "Substring")) return; + + int haystackLen, needleLen; + const char* haystack = wrenGetSlotBytes(vm, 0, &haystackLen); + const char* needle = wrenGetSlotBytes(vm, 1, &needleLen); + + if (needleLen == 0) { + wrenSetSlotDouble(vm, 0, 0); + return; + } + + int count = 0; + const char* p = haystack; + const char* end = haystack + haystackLen; + + while (p < end) { + const char* found = strstr(p, needle); + if (found) { + count++; + p = found + needleLen; + } else { + break; + } + } + + wrenSetSlotDouble(vm, 0, count); +} + +// Implements String.indexOf(_). +void stringIndexOf(WrenVM* vm) { + if (!validateString(vm, 1, "Substring")) return; + + int haystackLen, needleLen; + const char* haystack = wrenGetSlotBytes(vm, 0, &haystackLen); + const char* needle = wrenGetSlotBytes(vm, 1, &needleLen); + + if (needleLen == 0) { + wrenSetSlotDouble(vm, 0, 0); + return; + } + + const char* found = strstr(haystack, needle); + if (found) { + wrenSetSlotDouble(vm, 0, found - haystack); + } else { + wrenSetSlotDouble(vm, 0, -1); + } +} + +// Implements String.lastIndexOf(_). +void stringLastIndexOf(WrenVM* vm) { + if (!validateString(vm, 1, "Substring")) return; + + int haystackLen, needleLen; + const char* haystack = wrenGetSlotBytes(vm, 0, &haystackLen); + const char* needle = wrenGetSlotBytes(vm, 1, &needleLen); + + if (needleLen == 0 || needleLen > haystackLen) { + wrenSetSlotDouble(vm, 0, -1); + return; + } + + const char* lastFound = NULL; + const char* p = haystack; + + while ((p = strstr(p, needle)) != NULL) { + lastFound = p; + p++; + } + + if (lastFound) { + wrenSetSlotDouble(vm, 0, lastFound - haystack); + } else { + wrenSetSlotDouble(vm, 0, -1); + } +} + +// Implements String.contains(_). +void stringContains(WrenVM* vm) { + if (!validateString(vm, 1, "Substring")) return; + + int haystackLen, needleLen; + const char* haystack = wrenGetSlotBytes(vm, 0, &haystackLen); + const char* needle = wrenGetSlotBytes(vm, 1, &needleLen); + + if (needleLen == 0) { + wrenSetSlotBool(vm, 0, true); + return; + } + + wrenSetSlotBool(vm, 0, strstr(haystack, needle) != NULL); +} + +// Implements String.toInt(). +void stringToInt(WrenVM* vm) { + int len; + const char* str = wrenGetSlotBytes(vm, 0, &len); + + char* endptr; + errno = 0; + long value = strtol(str, &endptr, 10); + + if (errno != 0 || endptr == str || *endptr != '\0') { + wrenSetSlotNull(vm, 0); + } else { + wrenSetSlotDouble(vm, 0, (double)value); + } +} + +// Implements String.toNum(). +void stringToNum(WrenVM* vm) { + int len; + const char* str = wrenGetSlotBytes(vm, 0, &len); + + char* endptr; + errno = 0; + double value = strtod(str, &endptr); + + if (errno != 0 || endptr == str || *endptr != '\0') { + wrenSetSlotNull(vm, 0); + } else { + wrenSetSlotDouble(vm, 0, value); + } +} + +// Implements String.reverse(). +void stringReverse(WrenVM* vm) { + int len; + const char* str = wrenGetSlotBytes(vm, 0, &len); + + char* result = (char*)malloc(len + 1); + if (!result) { + wrenSetSlotString(vm, 0, "Memory allocation failed."); + wrenAbortFiber(vm, 0); + return; + } + + for (int i = 0; i < len; i++) { + result[i] = str[len - 1 - i]; + } + result[len] = '\0'; + + wrenSetSlotString(vm, 0, result); + free(result); +} + +// Implements String.repeat(_). +void stringRepeat(WrenVM* vm) { + if (!validateNumber(vm, 1, "Count")) return; + + int len; + const char* str = wrenGetSlotBytes(vm, 0, &len); + int count = (int)wrenGetSlotDouble(vm, 1); + + if (count < 0) { + wrenSetSlotString(vm, 0, "Count must be non-negative."); + wrenAbortFiber(vm, 0); + return; + } + + if (count == 0 || len == 0) { + wrenSetSlotString(vm, 0, ""); + return; + } + + size_t resultLen = len * count; + char* result = (char*)malloc(resultLen + 1); + if (!result) { + wrenSetSlotString(vm, 0, "Memory allocation failed."); + wrenAbortFiber(vm, 0); + return; + } + + for (int i = 0; i < count; i++) { + memcpy(result + (i * len), str, len); + } + result[resultLen] = '\0'; + + wrenSetSlotString(vm, 0, result); + free(result); +} + +// Implements String.padStart(_, _). +void stringPadStart(WrenVM* vm) { + if (!validateNumber(vm, 1, "Length")) return; + if (!validateString(vm, 2, "PadString")) return; + + int strLen; + const char* str = wrenGetSlotBytes(vm, 0, &strLen); + int targetLen = (int)wrenGetSlotDouble(vm, 1); + int padLen; + const char* padStr = wrenGetSlotBytes(vm, 2, &padLen); + + if (targetLen <= strLen || padLen == 0) { + wrenSetSlotString(vm, 0, str); + return; + } + + int padNeeded = targetLen - strLen; + char* result = (char*)malloc(targetLen + 1); + if (!result) { + wrenSetSlotString(vm, 0, "Memory allocation failed."); + wrenAbortFiber(vm, 0); + return; + } + + int pos = 0; + while (pos < padNeeded) { + int copyLen = (padNeeded - pos < padLen) ? (padNeeded - pos) : padLen; + memcpy(result + pos, padStr, copyLen); + pos += copyLen; + } + + memcpy(result + padNeeded, str, strLen); + result[targetLen] = '\0'; + + wrenSetSlotString(vm, 0, result); + free(result); +} + +// Implements String.padEnd(_, _). +void stringPadEnd(WrenVM* vm) { + if (!validateNumber(vm, 1, "Length")) return; + if (!validateString(vm, 2, "PadString")) return; + + int strLen; + const char* str = wrenGetSlotBytes(vm, 0, &strLen); + int targetLen = (int)wrenGetSlotDouble(vm, 1); + int padLen; + const char* padStr = wrenGetSlotBytes(vm, 2, &padLen); + + if (targetLen <= strLen || padLen == 0) { + wrenSetSlotString(vm, 0, str); + return; + } + + int padNeeded = targetLen - strLen; + char* result = (char*)malloc(targetLen + 1); + if (!result) { + wrenSetSlotString(vm, 0, "Memory allocation failed."); + wrenAbortFiber(vm, 0); + return; + } + + memcpy(result, str, strLen); + + int pos = strLen; + while (pos < targetLen) { + int copyLen = (targetLen - pos < padLen) ? (targetLen - pos) : padLen; + memcpy(result + pos, padStr, copyLen); + pos += copyLen; + } + + result[targetLen] = '\0'; + + wrenSetSlotString(vm, 0, result); + free(result); +} + +// Implements String.charAt(_). +void stringCharAt(WrenVM* vm) { + if (!validateNumber(vm, 1, "Index")) return; + + int len; + const char* str = wrenGetSlotBytes(vm, 0, &len); + int index = (int)wrenGetSlotDouble(vm, 1); + + if (index < 0 || index >= len) { + wrenSetSlotString(vm, 0, ""); + return; + } + + char result[2] = { str[index], '\0' }; + wrenSetSlotString(vm, 0, result); +} + +// Implements String.charCodeAt(_). +void stringCharCodeAt(WrenVM* vm) { + if (!validateNumber(vm, 1, "Index")) return; + + int len; + const char* str = wrenGetSlotBytes(vm, 0, &len); + int index = (int)wrenGetSlotDouble(vm, 1); + + if (index < 0 || index >= len) { + wrenSetSlotNull(vm, 0); + return; + } + + wrenSetSlotDouble(vm, 0, (unsigned char)str[index]); +} + +// Implements String.isNumeric(). +void stringIsNumeric(WrenVM* vm) { + int len; + const char* str = wrenGetSlotBytes(vm, 0, &len); + + if (len == 0) { + wrenSetSlotBool(vm, 0, false); + return; + } + + for (int i = 0; i < len; i++) { + if (!isdigit((unsigned char)str[i])) { + wrenSetSlotBool(vm, 0, false); + return; + } + } + + wrenSetSlotBool(vm, 0, true); +} + +// Implements String.isAlpha(). +void stringIsAlpha(WrenVM* vm) { + int len; + const char* str = wrenGetSlotBytes(vm, 0, &len); + + if (len == 0) { + wrenSetSlotBool(vm, 0, false); + return; + } + + for (int i = 0; i < len; i++) { + if (!isalpha((unsigned char)str[i])) { + wrenSetSlotBool(vm, 0, false); + return; + } + } + + wrenSetSlotBool(vm, 0, true); +} + +// Implements String.isAlphaNumeric(). +void stringIsAlphaNumeric(WrenVM* vm) { + int len; + const char* str = wrenGetSlotBytes(vm, 0, &len); + + if (len == 0) { + wrenSetSlotBool(vm, 0, false); + return; + } + + for (int i = 0; i < len; i++) { + if (!isalnum((unsigned char)str[i])) { + wrenSetSlotBool(vm, 0, false); + return; + } + } + + wrenSetSlotBool(vm, 0, true); +} + +// Implements String.isEmpty(). +void stringIsEmpty(WrenVM* vm) { + int len; + wrenGetSlotBytes(vm, 0, &len); + wrenSetSlotBool(vm, 0, len == 0); +} + +// Implements String.isWhitespace(). +void stringIsWhitespace(WrenVM* vm) { + int len; + const char* str = wrenGetSlotBytes(vm, 0, &len); + + if (len == 0) { + wrenSetSlotBool(vm, 0, true); + return; + } + + for (int i = 0; i < len; i++) { + if (!isspace((unsigned char)str[i])) { + wrenSetSlotBool(vm, 0, false); + return; + } + } + + wrenSetSlotBool(vm, 0, true); +} + +// Implements String.capitalize(). +void stringCapitalize(WrenVM* vm) { + int len; + const char* str = wrenGetSlotBytes(vm, 0, &len); + + if (len == 0) { + wrenSetSlotString(vm, 0, ""); + return; + } + + char* result = (char*)malloc(len + 1); + if (!result) { + wrenSetSlotString(vm, 0, "Memory allocation failed."); + wrenAbortFiber(vm, 0); + return; + } + + result[0] = toupper((unsigned char)str[0]); + for (int i = 1; i < len; i++) { + result[i] = tolower((unsigned char)str[i]); + } + result[len] = '\0'; + + wrenSetSlotString(vm, 0, result); + free(result); +} + +// Implements String.toCamelCase(). +void stringToCamelCase(WrenVM* vm) { + int len; + const char* str = wrenGetSlotBytes(vm, 0, &len); + + char* result = (char*)malloc(len + 1); + if (!result) { + wrenSetSlotString(vm, 0, "Memory allocation failed."); + wrenAbortFiber(vm, 0); + return; + } + + int j = 0; + bool capitalizeNext = false; + + for (int i = 0; i < len; i++) { + if (str[i] == '_' || str[i] == '-' || isspace((unsigned char)str[i])) { + capitalizeNext = true; + } else { + if (capitalizeNext && j > 0) { + result[j++] = toupper((unsigned char)str[i]); + capitalizeNext = false; + } else { + result[j++] = (j == 0) ? tolower((unsigned char)str[i]) : str[i]; + } + } + } + result[j] = '\0'; + + wrenSetSlotString(vm, 0, result); + free(result); +} + +// Implements String.toSnakeCase(). +void stringToSnakeCase(WrenVM* vm) { + int len; + const char* str = wrenGetSlotBytes(vm, 0, &len); + + // Allocate extra space for underscores + char* result = (char*)malloc(len * 2 + 1); + if (!result) { + wrenSetSlotString(vm, 0, "Memory allocation failed."); + wrenAbortFiber(vm, 0); + return; + } + + int j = 0; + for (int i = 0; i < len; i++) { + if (isupper((unsigned char)str[i]) && i > 0 && !isupper((unsigned char)str[i-1])) { + result[j++] = '_'; + } + if (str[i] == '-' || isspace((unsigned char)str[i])) { + result[j++] = '_'; + } else { + result[j++] = tolower((unsigned char)str[i]); + } + } + result[j] = '\0'; + + wrenSetSlotString(vm, 0, result); + free(result); +} + +// Implements String.toKebabCase(). +void stringToKebabCase(WrenVM* vm) { + int len; + const char* str = wrenGetSlotBytes(vm, 0, &len); + + // Allocate extra space for hyphens + char* result = (char*)malloc(len * 2 + 1); + if (!result) { + wrenSetSlotString(vm, 0, "Memory allocation failed."); + wrenAbortFiber(vm, 0); + return; + } + + int j = 0; + for (int i = 0; i < len; i++) { + if (isupper((unsigned char)str[i]) && i > 0 && !isupper((unsigned char)str[i-1])) { + result[j++] = '-'; + } + if (str[i] == '_' || isspace((unsigned char)str[i])) { + result[j++] = '-'; + } else { + result[j++] = tolower((unsigned char)str[i]); + } + } + result[j] = '\0'; + + wrenSetSlotString(vm, 0, result); + free(result); +} + +// Implements String.toPascalCase(). +void stringToPascalCase(WrenVM* vm) { + int len; + const char* str = wrenGetSlotBytes(vm, 0, &len); + + char* result = (char*)malloc(len + 1); + if (!result) { + wrenSetSlotString(vm, 0, "Memory allocation failed."); + wrenAbortFiber(vm, 0); + return; + } + + int j = 0; + bool capitalizeNext = true; + + for (int i = 0; i < len; i++) { + if (str[i] == '_' || str[i] == '-' || isspace((unsigned char)str[i])) { + capitalizeNext = true; + } else { + if (capitalizeNext) { + result[j++] = toupper((unsigned char)str[i]); + capitalizeNext = false; + } else { + result[j++] = str[i]; + } + } + } + result[j] = '\0'; + + wrenSetSlotString(vm, 0, result); + free(result); +} + +// Implements String.swapCase(). +void stringSwapCase(WrenVM* vm) { + int len; + const char* str = wrenGetSlotBytes(vm, 0, &len); + + char* result = (char*)malloc(len + 1); + if (!result) { + wrenSetSlotString(vm, 0, "Memory allocation failed."); + wrenAbortFiber(vm, 0); + return; + } + + for (int i = 0; i < len; i++) { + if (isupper((unsigned char)str[i])) { + result[i] = tolower((unsigned char)str[i]); + } else if (islower((unsigned char)str[i])) { + result[i] = toupper((unsigned char)str[i]); + } else { + result[i] = str[i]; + } + } + result[len] = '\0'; + + wrenSetSlotString(vm, 0, result); + free(result); +} + +// Implements String.substring(_, _). +void stringSubstring(WrenVM* vm) { + if (!validateNumber(vm, 1, "Start")) return; + if (!validateNumber(vm, 2, "End")) return; + + int len; + const char* str = wrenGetSlotBytes(vm, 0, &len); + int start = (int)wrenGetSlotDouble(vm, 1); + int end = (int)wrenGetSlotDouble(vm, 2); + + // Handle negative indices + if (start < 0) start = 0; + if (end < 0) end = 0; + if (start > len) start = len; + if (end > len) end = len; + + // Swap if start > end + if (start > end) { + int temp = start; + start = end; + end = temp; + } + + wrenSetSlotBytes(vm, 0, str + start, end - start); +} + +// Implements String.slice(_, _). +void stringSlice(WrenVM* vm) { + if (!validateNumber(vm, 1, "Start")) return; + + int len; + const char* str = wrenGetSlotBytes(vm, 0, &len); + int start = (int)wrenGetSlotDouble(vm, 1); + int end = len; + + // Check if end parameter is provided + if (wrenGetSlotCount(vm) > 2 && wrenGetSlotType(vm, 2) == WREN_TYPE_NUM) { + end = (int)wrenGetSlotDouble(vm, 2); + } + + // Handle negative indices + if (start < 0) start = len + start; + if (end < 0) end = len + end; + + // Clamp values + if (start < 0) start = 0; + if (end < 0) end = 0; + if (start > len) start = len; + if (end > len) end = len; + + if (start >= end) { + wrenSetSlotString(vm, 0, ""); + return; + } + + wrenSetSlotBytes(vm, 0, str + start, end - start); +} + +// Implements String.toBytes(). +void stringToBytes(WrenVM* vm) { + int len; + const char* str = wrenGetSlotBytes(vm, 0, &len); + + wrenSetSlotNewList(vm, 0); + + for (int i = 0; i < len; i++) { + wrenSetSlotDouble(vm, 1, (unsigned char)str[i]); + wrenInsertInList(vm, 0, -1, 1); + } +} + +// Implements String.fromCharCode(_). +void stringFromCharCode(WrenVM* vm) { + if (!validateNumber(vm, 1, "CharCode")) return; + + int code = (int)wrenGetSlotDouble(vm, 1); + + if (code < 0 || code > 255) { + wrenSetSlotString(vm, 0, ""); + return; + } + + char result[2] = { (char)code, '\0' }; + wrenSetSlotString(vm, 0, result); +} + +// Implements String.length(). +void stringLength(WrenVM* vm) { + int len; + const char* str = wrenGetSlotBytes(vm, 0, &len); + wrenSetSlotDouble(vm, 0, (double)len); +} + +// Static method: String.join(_, _). +void stringJoin(WrenVM* vm) { + // First argument should be a list + if (wrenGetSlotType(vm, 1) != WREN_TYPE_LIST) { + wrenSetSlotString(vm, 0, "First argument must be a list."); + wrenAbortFiber(vm, 0); + return; + } + + if (!validateString(vm, 2, "Separator")) return; + + int sepLen; + const char* separator = wrenGetSlotBytes(vm, 2, &sepLen); + + int count = wrenGetListCount(vm, 1); + if (count == 0) { + wrenSetSlotString(vm, 0, ""); + return; + } + + // Calculate total length needed + size_t totalLen = 0; + for (int i = 0; i < count; i++) { + wrenGetListElement(vm, 1, i, 0); + if (wrenGetSlotType(vm, 0) == WREN_TYPE_STRING) { + int itemLen; + wrenGetSlotBytes(vm, 0, &itemLen); + totalLen += itemLen; + } + } + totalLen += (count - 1) * sepLen; + + char* result = (char*)malloc(totalLen + 1); + if (!result) { + wrenSetSlotString(vm, 0, "Memory allocation failed."); + wrenAbortFiber(vm, 0); + return; + } + + char* p = result; + for (int i = 0; i < count; i++) { + wrenGetListElement(vm, 1, i, 0); + if (wrenGetSlotType(vm, 0) == WREN_TYPE_STRING) { + int itemLen; + const char* item = wrenGetSlotBytes(vm, 0, &itemLen); + memcpy(p, item, itemLen); + p += itemLen; + + if (i < count - 1) { + memcpy(p, separator, sepLen); + p += sepLen; + } + } + } + *p = '\0'; + + wrenSetSlotString(vm, 0, result); + free(result); +} + // Binds the foreign methods for the String class. WrenForeignMethodFn bindStringForeignMethod(WrenVM* vm, const char* module, const char* className, bool isStatic, const char* signature) { if (strcmp(module, "main") != 0 && strcmp(module, "core") != 0) return NULL; - if (strcmp(className, "String") != 0 || isStatic) return NULL; - - if (strcmp(signature, "endsWith(_)") == 0) return stringEndsWith; - if (strcmp(signature, "startsWith(_)") == 0) return stringStartsWith; - if (strcmp(signature, "replace(_,_)") == 0) return stringReplace; - if (strcmp(signature, "split(_)") == 0) return stringSplit; - + if (strcmp(className, "String") != 0) return NULL; + + // Instance methods + if (!isStatic) { + if (strcmp(signature, "endsWith(_)") == 0) return stringEndsWith; + if (strcmp(signature, "startsWith(_)") == 0) return stringStartsWith; + if (strcmp(signature, "replace(_,_)") == 0) return stringReplace; + if (strcmp(signature, "split(_)") == 0) return stringSplit; + if (strcmp(signature, "toUpper()") == 0) return stringToUpper; + if (strcmp(signature, "toLower()") == 0) return stringToLower; + if (strcmp(signature, "trim()") == 0) return stringTrim; + if (strcmp(signature, "trimStart()") == 0) return stringTrimStart; + if (strcmp(signature, "trimEnd()") == 0) return stringTrimEnd; + if (strcmp(signature, "count(_)") == 0) return stringCount; + if (strcmp(signature, "indexOf(_)") == 0) return stringIndexOf; + if (strcmp(signature, "lastIndexOf(_)") == 0) return stringLastIndexOf; + if (strcmp(signature, "contains(_)") == 0) return stringContains; + if (strcmp(signature, "toInt()") == 0) return stringToInt; + if (strcmp(signature, "toNum()") == 0) return stringToNum; + if (strcmp(signature, "reverse()") == 0) return stringReverse; + if (strcmp(signature, "repeat(_)") == 0) return stringRepeat; + if (strcmp(signature, "padStart(_,_)") == 0) return stringPadStart; + if (strcmp(signature, "padEnd(_,_)") == 0) return stringPadEnd; + if (strcmp(signature, "charAt(_)") == 0) return stringCharAt; + if (strcmp(signature, "charCodeAt(_)") == 0) return stringCharCodeAt; + if (strcmp(signature, "isNumeric()") == 0) return stringIsNumeric; + if (strcmp(signature, "isAlpha()") == 0) return stringIsAlpha; + if (strcmp(signature, "isAlphaNumeric()") == 0) return stringIsAlphaNumeric; + if (strcmp(signature, "isEmpty()") == 0) return stringIsEmpty; + if (strcmp(signature, "isWhitespace()") == 0) return stringIsWhitespace; + if (strcmp(signature, "capitalize()") == 0) return stringCapitalize; + if (strcmp(signature, "toCamelCase()") == 0) return stringToCamelCase; + if (strcmp(signature, "toSnakeCase()") == 0) return stringToSnakeCase; + if (strcmp(signature, "toKebabCase()") == 0) return stringToKebabCase; + if (strcmp(signature, "toPascalCase()") == 0) return stringToPascalCase; + if (strcmp(signature, "swapCase()") == 0) return stringSwapCase; + if (strcmp(signature, "substring(_,_)") == 0) return stringSubstring; + if (strcmp(signature, "slice(_,_)") == 0) return stringSlice; + if (strcmp(signature, "slice(_)") == 0) return stringSlice; + if (strcmp(signature, "toBytes()") == 0) return stringToBytes; + if (strcmp(signature, "length()") == 0) return stringLength; // Added length method + } + + // Static methods + if (isStatic) { + if (strcmp(signature, "join(_,_)") == 0) return stringJoin; + if (strcmp(signature, "fromCharCode(_)") == 0) return stringFromCharCode; + } + return NULL; } diff --git a/test.db-journal b/test.db-journal deleted file mode 100644 index e1c57ba..0000000 Binary files a/test.db-journal and /dev/null differ diff --git a/test.wren b/test.wren new file mode 100644 index 0000000..f32bdd9 --- /dev/null +++ b/test.wren @@ -0,0 +1,14 @@ + +var z= "AAAS" +var duu = z.count +// System.print(z.toUpper()) + +var a = "te_st_t_je".split("_").join("/").split("/").join("*").replace("*","~") +//a = a.toCamelCase() +System.print(duu) +System.print(a) +System.print(a) +System.print(a) +System.print(a) + + diff --git a/wren b/wren index 23f3318..562d90d 100755 Binary files a/wren and b/wren differ