New modules.
WrenCI / linux (push) Waiting to run
WrenCI / mac (push) Waiting to run
WrenCI / windows (push) Waiting to run

This commit is contained in:
2026-01-24 19:35:43 +01:00
parent 1a08b3adf0
commit 236d69c2d7
118 changed files with 315687 additions and 6 deletions
+204 -1
View File
@@ -3,16 +3,68 @@
#include "modules.h"
#include "base64.wren.inc"
#include "crypto.wren.inc"
#include "datetime.wren.inc"
#include "dns.wren.inc"
#include "env.wren.inc"
#include "http.wren.inc"
#include "io.wren.inc"
#include "jinja.wren.inc"
#include "json.wren.inc"
#include "math.wren.inc"
#include "net.wren.inc"
#include "os.wren.inc"
#include "regex.wren.inc"
#include "repl.wren.inc"
#include "scheduler.wren.inc"
#include "signal.wren.inc"
#include "sqlite.wren.inc"
#include "subprocess.wren.inc"
#include "timer.wren.inc"
#include "tls.wren.inc"
#include "websocket.wren.inc"
extern void base64Encode(WrenVM* vm);
extern void base64Decode(WrenVM* vm);
extern void cryptoRandomBytes(WrenVM* vm);
extern void cryptoMd5(WrenVM* vm);
extern void cryptoSha1(WrenVM* vm);
extern void cryptoSha256(WrenVM* vm);
extern void datetimeNow(WrenVM* vm);
extern void datetimeFromTimestamp(WrenVM* vm);
extern void datetimeFormat(WrenVM* vm);
extern void datetimeComponent(WrenVM* vm);
extern void dnsLookup(WrenVM* vm);
extern void directoryList(WrenVM* vm);
extern void directoryCreate(WrenVM* vm);
extern void directoryDelete(WrenVM* vm);
extern void envGet(WrenVM* vm);
extern void envSet(WrenVM* vm);
extern void envDelete(WrenVM* vm);
extern void envAll(WrenVM* vm);
extern void jsonParse(WrenVM* vm);
extern void mathSin(WrenVM* vm);
extern void mathCos(WrenVM* vm);
extern void mathTan(WrenVM* vm);
extern void mathAsin(WrenVM* vm);
extern void mathAcos(WrenVM* vm);
extern void mathAtan(WrenVM* vm);
extern void mathAtan2(WrenVM* vm);
extern void mathSinh(WrenVM* vm);
extern void mathCosh(WrenVM* vm);
extern void mathTanh(WrenVM* vm);
extern void mathLog(WrenVM* vm);
extern void mathLog10(WrenVM* vm);
extern void mathLog2(WrenVM* vm);
extern void mathExp(WrenVM* vm);
extern void mathPow(WrenVM* vm);
extern void mathSqrt(WrenVM* vm);
extern void mathCbrt(WrenVM* vm);
extern void mathCeil(WrenVM* vm);
extern void mathFloor(WrenVM* vm);
extern void mathRound(WrenVM* vm);
extern void mathAbs(WrenVM* vm);
extern void fileAllocate(WrenVM* vm);
extern void fileFinalize(void* data);
extern void fileDelete(WrenVM* vm);
@@ -68,6 +120,35 @@ extern void serverFinalize(void* data);
extern void serverBind(WrenVM* vm);
extern void serverAccept(WrenVM* vm);
extern void serverClose(WrenVM* vm);
extern void signalTrap(WrenVM* vm);
extern void signalIgnore(WrenVM* vm);
extern void signalReset(WrenVM* vm);
extern void sqliteAllocate(WrenVM* vm);
extern void sqliteFinalize(void* data);
extern void sqliteExecute(WrenVM* vm);
extern void sqliteExecuteParams(WrenVM* vm);
extern void sqliteQuery(WrenVM* vm);
extern void sqliteQueryParams(WrenVM* vm);
extern void sqliteClose(WrenVM* vm);
extern void sqliteLastInsertId(WrenVM* vm);
extern void sqliteChanges(WrenVM* vm);
extern void subprocessRun(WrenVM* vm);
extern void regexAllocate(WrenVM* vm);
extern void regexFinalize(void* data);
extern void regexTest(WrenVM* vm);
extern void regexMatch(WrenVM* vm);
extern void regexMatchAll(WrenVM* vm);
extern void regexReplace(WrenVM* vm);
extern void regexReplaceAll(WrenVM* vm);
extern void regexSplit(WrenVM* vm);
extern void regexPattern(WrenVM* vm);
extern void regexFlags(WrenVM* vm);
extern void tlsSocketAllocate(WrenVM* vm);
extern void tlsSocketFinalize(void* data);
extern void tlsSocketConnect(WrenVM* vm);
extern void tlsSocketWrite(WrenVM* vm);
extern void tlsSocketRead(WrenVM* vm);
extern void tlsSocketClose(WrenVM* vm);
// The maximum number of foreign methods a single class defines. Ideally, we
// would use variable-length arrays for each class in the table below, but
@@ -77,7 +158,7 @@ extern void serverClose(WrenVM* vm);
// If you add a new method to the longest class below, make sure to bump this.
// Note that it also includes an extra slot for the sentinel value indicating
// the end of the list.
#define MAX_METHODS_PER_CLASS 14
#define MAX_METHODS_PER_CLASS 24
// The maximum number of foreign classes a single built-in module defines.
//
@@ -137,6 +218,45 @@ typedef struct
// The array of built-in modules.
static ModuleRegistry modules[] =
{
MODULE(base64)
CLASS(Base64)
STATIC_METHOD("encode(_)", base64Encode)
STATIC_METHOD("decode(_)", base64Decode)
END_CLASS
END_MODULE
MODULE(crypto)
CLASS(Crypto)
STATIC_METHOD("randomBytes_(_,_)", cryptoRandomBytes)
END_CLASS
CLASS(Hash)
STATIC_METHOD("md5_(_)", cryptoMd5)
STATIC_METHOD("sha1_(_)", cryptoSha1)
STATIC_METHOD("sha256_(_)", cryptoSha256)
END_CLASS
END_MODULE
MODULE(datetime)
CLASS(DateTime)
STATIC_METHOD("now_()", datetimeNow)
STATIC_METHOD("fromTimestamp_(_)", datetimeFromTimestamp)
STATIC_METHOD("format_(_,_)", datetimeFormat)
STATIC_METHOD("component_(_,_)", datetimeComponent)
END_CLASS
END_MODULE
MODULE(dns)
CLASS(Dns)
STATIC_METHOD("lookup_(_,_,_)", dnsLookup)
END_CLASS
END_MODULE
MODULE(env)
CLASS(Environment)
STATIC_METHOD("get(_)", envGet)
STATIC_METHOD("set(_,_)", envSet)
STATIC_METHOD("delete(_)", envDelete)
STATIC_METHOD("all", envAll)
END_CLASS
END_MODULE
MODULE(http)
END_MODULE
MODULE(io)
CLASS(Directory)
STATIC_METHOD("create_(_,_)", directoryCreate)
@@ -186,6 +306,38 @@ static ModuleRegistry modules[] =
STATIC_METHOD("write(_)", stderrWrite)
END_CLASS
END_MODULE
MODULE(jinja)
END_MODULE
MODULE(json)
CLASS(Json)
STATIC_METHOD("parse(_)", jsonParse)
END_CLASS
END_MODULE
MODULE(math)
CLASS(Math)
STATIC_METHOD("sin(_)", mathSin)
STATIC_METHOD("cos(_)", mathCos)
STATIC_METHOD("tan(_)", mathTan)
STATIC_METHOD("asin(_)", mathAsin)
STATIC_METHOD("acos(_)", mathAcos)
STATIC_METHOD("atan(_)", mathAtan)
STATIC_METHOD("atan2(_,_)", mathAtan2)
STATIC_METHOD("sinh(_)", mathSinh)
STATIC_METHOD("cosh(_)", mathCosh)
STATIC_METHOD("tanh(_)", mathTanh)
STATIC_METHOD("log(_)", mathLog)
STATIC_METHOD("log10(_)", mathLog10)
STATIC_METHOD("log2(_)", mathLog2)
STATIC_METHOD("exp(_)", mathExp)
STATIC_METHOD("pow(_,_)", mathPow)
STATIC_METHOD("sqrt(_)", mathSqrt)
STATIC_METHOD("cbrt(_)", mathCbrt)
STATIC_METHOD("ceil(_)", mathCeil)
STATIC_METHOD("floor(_)", mathFloor)
STATIC_METHOD("round(_)", mathRound)
STATIC_METHOD("abs(_)", mathAbs)
END_CLASS
END_MODULE
MODULE(net)
CLASS(Socket)
ALLOCATE(socketAllocate)
@@ -218,6 +370,20 @@ static ModuleRegistry modules[] =
STATIC_METHOD("version", processVersion)
END_CLASS
END_MODULE
MODULE(regex)
CLASS(Regex)
ALLOCATE(regexAllocate)
FINALIZE(regexFinalize)
METHOD("test(_)", regexTest)
METHOD("match(_)", regexMatch)
METHOD("matchAll(_)", regexMatchAll)
METHOD("replace(_,_)", regexReplace)
METHOD("replaceAll(_,_)", regexReplaceAll)
METHOD("split(_)", regexSplit)
METHOD("pattern", regexPattern)
METHOD("flags", regexFlags)
END_CLASS
END_MODULE
MODULE(repl)
END_MODULE
MODULE(scheduler)
@@ -225,11 +391,48 @@ static ModuleRegistry modules[] =
STATIC_METHOD("captureMethods_()", schedulerCaptureMethods)
END_CLASS
END_MODULE
MODULE(signal)
CLASS(Signal)
STATIC_METHOD("trap_(_,_)", signalTrap)
STATIC_METHOD("ignore_(_)", signalIgnore)
STATIC_METHOD("reset_(_)", signalReset)
END_CLASS
END_MODULE
MODULE(sqlite)
CLASS(Database)
ALLOCATE(sqliteAllocate)
FINALIZE(sqliteFinalize)
METHOD("execute(_)", sqliteExecute)
METHOD("execute(_,_)", sqliteExecuteParams)
METHOD("query(_)", sqliteQuery)
METHOD("query(_,_)", sqliteQueryParams)
METHOD("close()", sqliteClose)
METHOD("lastInsertId", sqliteLastInsertId)
METHOD("changes", sqliteChanges)
END_CLASS
END_MODULE
MODULE(subprocess)
CLASS(Subprocess)
STATIC_METHOD("run_(_,_)", subprocessRun)
END_CLASS
END_MODULE
MODULE(timer)
CLASS(Timer)
STATIC_METHOD("startTimer_(_,_)", timerStartTimer)
END_CLASS
END_MODULE
MODULE(tls)
CLASS(TlsSocket)
ALLOCATE(tlsSocketAllocate)
FINALIZE(tlsSocketFinalize)
METHOD("connect_(_,_,_,_)", tlsSocketConnect)
METHOD("write_(_,_)", tlsSocketWrite)
METHOD("read_(_)", tlsSocketRead)
METHOD("close_()", tlsSocketClose)
END_CLASS
END_MODULE
MODULE(websocket)
END_MODULE
SENTINEL_MODULE
};
+163
View File
@@ -0,0 +1,163 @@
// retoor <retoor@molodetz.nl>
#include <stdlib.h>
#include <string.h>
#include "base64.h"
#include "wren.h"
static const char base64_chars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const unsigned char base64_lookup[256] = {
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
};
void base64Encode(WrenVM* vm) {
int length = 0;
const char* data = wrenGetSlotBytes(vm, 1, &length);
if (data == NULL || length == 0) {
wrenSetSlotString(vm, 0, "");
return;
}
size_t outputLen = 4 * ((length + 2) / 3);
char* output = (char*)malloc(outputLen + 1);
if (output == NULL) {
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
size_t i = 0;
size_t j = 0;
unsigned char a3[3];
unsigned char a4[4];
while (length--) {
a3[i++] = (unsigned char)*(data++);
if (i == 3) {
a4[0] = (a3[0] & 0xfc) >> 2;
a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4);
a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6);
a4[3] = a3[2] & 0x3f;
for (i = 0; i < 4; i++) {
output[j++] = base64_chars[a4[i]];
}
i = 0;
}
}
if (i > 0) {
for (size_t k = i; k < 3; k++) {
a3[k] = '\0';
}
a4[0] = (a3[0] & 0xfc) >> 2;
a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4);
a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6);
for (size_t k = 0; k < i + 1; k++) {
output[j++] = base64_chars[a4[k]];
}
while (i++ < 3) {
output[j++] = '=';
}
}
output[j] = '\0';
wrenSetSlotString(vm, 0, output);
free(output);
}
void base64Decode(WrenVM* vm) {
const char* data = wrenGetSlotString(vm, 1);
if (data == NULL) {
wrenSetSlotString(vm, 0, "");
return;
}
size_t inputLen = strlen(data);
if (inputLen == 0) {
wrenSetSlotString(vm, 0, "");
return;
}
size_t padding = 0;
if (inputLen >= 1 && data[inputLen - 1] == '=') padding++;
if (inputLen >= 2 && data[inputLen - 2] == '=') padding++;
size_t outputLen = (inputLen / 4) * 3 - padding;
char* output = (char*)malloc(outputLen + 1);
if (output == NULL) {
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
size_t i = 0;
size_t j = 0;
unsigned char a4[4];
unsigned char a3[3];
while (inputLen--) {
unsigned char c = (unsigned char)*data++;
if (c == '=') break;
if (base64_lookup[c] == 64) continue;
a4[i++] = c;
if (i == 4) {
for (i = 0; i < 4; i++) {
a4[i] = base64_lookup[a4[i]];
}
a3[0] = (a4[0] << 2) + ((a4[1] & 0x30) >> 4);
a3[1] = ((a4[1] & 0x0f) << 4) + ((a4[2] & 0x3c) >> 2);
a3[2] = ((a4[2] & 0x03) << 6) + a4[3];
for (i = 0; i < 3; i++) {
output[j++] = (char)a3[i];
}
i = 0;
}
}
if (i > 0) {
for (size_t k = i; k < 4; k++) {
a4[k] = 0;
}
for (size_t k = 0; k < 4; k++) {
a4[k] = base64_lookup[a4[k]];
}
a3[0] = (a4[0] << 2) + ((a4[1] & 0x30) >> 4);
a3[1] = ((a4[1] & 0x0f) << 4) + ((a4[2] & 0x3c) >> 2);
a3[2] = ((a4[2] & 0x03) << 6) + a4[3];
for (size_t k = 0; k < i - 1; k++) {
output[j++] = (char)a3[k];
}
}
output[j] = '\0';
wrenSetSlotBytes(vm, 0, output, j);
free(output);
}
+11
View File
@@ -0,0 +1,11 @@
// retoor <retoor@molodetz.nl>
#ifndef wren_base64_h
#define wren_base64_h
#include "wren.h"
void base64Encode(WrenVM* vm);
void base64Decode(WrenVM* vm);
#endif
+28
View File
@@ -0,0 +1,28 @@
// retoor <retoor@molodetz.nl>
class Base64 {
foreign static encode(data)
foreign static decode(data)
static encodeUrl(data) {
var result = encode(data)
result = result.replace("+", "-")
result = result.replace("/", "_")
while (result.endsWith("=")) {
result = result[0...-1]
}
return result
}
static decodeUrl(data) {
var result = data.replace("-", "+")
result = result.replace("_", "/")
var padding = 4 - result.count % 4
if (padding < 4) {
for (i in 0...padding) {
result = result + "="
}
}
return decode(result)
}
}
+32
View File
@@ -0,0 +1,32 @@
// Please do not edit this file. It has been generated automatically
// from `src/module/base64.wren` using `util/wren_to_c_string.py`
static const char* base64ModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"class Base64 {\n"
" foreign static encode(data)\n"
" foreign static decode(data)\n"
"\n"
" static encodeUrl(data) {\n"
" var result = encode(data)\n"
" result = result.replace(\"+\", \"-\")\n"
" result = result.replace(\"/\", \"_\")\n"
" while (result.endsWith(\"=\")) {\n"
" result = result[0...-1]\n"
" }\n"
" return result\n"
" }\n"
"\n"
" static decodeUrl(data) {\n"
" var result = data.replace(\"-\", \"+\")\n"
" result = result.replace(\"_\", \"/\")\n"
" var padding = 4 - result.count % 4\n"
" if (padding < 4) {\n"
" for (i in 0...padding) {\n"
" result = result + \"=\"\n"
" }\n"
" }\n"
" return decode(result)\n"
" }\n"
"}\n";
+373
View File
@@ -0,0 +1,373 @@
// retoor <retoor@molodetz.nl>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "crypto.h"
#include "scheduler.h"
#include "wren.h"
#include "vm.h"
#include "uv.h"
#define ROTL32(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
#define ROTR32(x, n) (((x) >> (n)) | ((x) << (32 - (n))))
static void md5Transform(uint32_t state[4], const uint8_t block[64]) {
static const uint32_t k[64] = {
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
};
static const uint8_t r[64] = {
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21
};
uint32_t m[16];
for (int i = 0; i < 16; i++) {
m[i] = block[i * 4] | (block[i * 4 + 1] << 8) |
(block[i * 4 + 2] << 16) | (block[i * 4 + 3] << 24);
}
uint32_t a = state[0], b = state[1], c = state[2], d = state[3];
for (int i = 0; i < 64; i++) {
uint32_t f, g;
if (i < 16) {
f = (b & c) | (~b & d);
g = i;
} else if (i < 32) {
f = (d & b) | (~d & c);
g = (5 * i + 1) % 16;
} else if (i < 48) {
f = b ^ c ^ d;
g = (3 * i + 5) % 16;
} else {
f = c ^ (b | ~d);
g = (7 * i) % 16;
}
uint32_t temp = d;
d = c;
c = b;
b = b + ROTL32(a + f + k[i] + m[g], r[i]);
a = temp;
}
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
}
static void md5(const uint8_t* data, size_t len, uint8_t digest[16]) {
uint32_t state[4] = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476};
uint8_t block[64];
size_t i;
for (i = 0; i + 64 <= len; i += 64) {
md5Transform(state, data + i);
}
size_t remaining = len - i;
memcpy(block, data + i, remaining);
block[remaining++] = 0x80;
if (remaining > 56) {
memset(block + remaining, 0, 64 - remaining);
md5Transform(state, block);
remaining = 0;
}
memset(block + remaining, 0, 56 - remaining);
uint64_t bits = len * 8;
for (int j = 0; j < 8; j++) {
block[56 + j] = (bits >> (j * 8)) & 0xFF;
}
md5Transform(state, block);
for (int j = 0; j < 4; j++) {
digest[j * 4] = state[j] & 0xFF;
digest[j * 4 + 1] = (state[j] >> 8) & 0xFF;
digest[j * 4 + 2] = (state[j] >> 16) & 0xFF;
digest[j * 4 + 3] = (state[j] >> 24) & 0xFF;
}
}
static void sha1Transform(uint32_t state[5], const uint8_t block[64]) {
uint32_t w[80];
for (int i = 0; i < 16; i++) {
w[i] = (block[i * 4] << 24) | (block[i * 4 + 1] << 16) |
(block[i * 4 + 2] << 8) | block[i * 4 + 3];
}
for (int i = 16; i < 80; i++) {
w[i] = ROTL32(w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16], 1);
}
uint32_t a = state[0], b = state[1], c = state[2], d = state[3], e = state[4];
for (int i = 0; i < 80; i++) {
uint32_t f, k;
if (i < 20) {
f = (b & c) | (~b & d);
k = 0x5A827999;
} else if (i < 40) {
f = b ^ c ^ d;
k = 0x6ED9EBA1;
} else if (i < 60) {
f = (b & c) | (b & d) | (c & d);
k = 0x8F1BBCDC;
} else {
f = b ^ c ^ d;
k = 0xCA62C1D6;
}
uint32_t temp = ROTL32(a, 5) + f + e + k + w[i];
e = d;
d = c;
c = ROTL32(b, 30);
b = a;
a = temp;
}
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
state[4] += e;
}
static void sha1(const uint8_t* data, size_t len, uint8_t digest[20]) {
uint32_t state[5] = {0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0};
uint8_t block[64];
size_t i;
for (i = 0; i + 64 <= len; i += 64) {
sha1Transform(state, data + i);
}
size_t remaining = len - i;
memcpy(block, data + i, remaining);
block[remaining++] = 0x80;
if (remaining > 56) {
memset(block + remaining, 0, 64 - remaining);
sha1Transform(state, block);
remaining = 0;
}
memset(block + remaining, 0, 56 - remaining);
uint64_t bits = len * 8;
for (int j = 0; j < 8; j++) {
block[56 + j] = (bits >> ((7 - j) * 8)) & 0xFF;
}
sha1Transform(state, block);
for (int j = 0; j < 5; j++) {
digest[j * 4] = (state[j] >> 24) & 0xFF;
digest[j * 4 + 1] = (state[j] >> 16) & 0xFF;
digest[j * 4 + 2] = (state[j] >> 8) & 0xFF;
digest[j * 4 + 3] = state[j] & 0xFF;
}
}
static void sha256Transform(uint32_t state[8], const uint8_t block[64]) {
static const uint32_t k[64] = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};
uint32_t w[64];
for (int i = 0; i < 16; i++) {
w[i] = (block[i * 4] << 24) | (block[i * 4 + 1] << 16) |
(block[i * 4 + 2] << 8) | block[i * 4 + 3];
}
for (int i = 16; i < 64; i++) {
uint32_t s0 = ROTR32(w[i-15], 7) ^ ROTR32(w[i-15], 18) ^ (w[i-15] >> 3);
uint32_t s1 = ROTR32(w[i-2], 17) ^ ROTR32(w[i-2], 19) ^ (w[i-2] >> 10);
w[i] = w[i-16] + s0 + w[i-7] + s1;
}
uint32_t a = state[0], b = state[1], c = state[2], d = state[3];
uint32_t e = state[4], f = state[5], g = state[6], h = state[7];
for (int i = 0; i < 64; i++) {
uint32_t S1 = ROTR32(e, 6) ^ ROTR32(e, 11) ^ ROTR32(e, 25);
uint32_t ch = (e & f) ^ (~e & g);
uint32_t temp1 = h + S1 + ch + k[i] + w[i];
uint32_t S0 = ROTR32(a, 2) ^ ROTR32(a, 13) ^ ROTR32(a, 22);
uint32_t maj = (a & b) ^ (a & c) ^ (b & c);
uint32_t temp2 = S0 + maj;
h = g; g = f; f = e; e = d + temp1;
d = c; c = b; b = a; a = temp1 + temp2;
}
state[0] += a; state[1] += b; state[2] += c; state[3] += d;
state[4] += e; state[5] += f; state[6] += g; state[7] += h;
}
static void sha256(const uint8_t* data, size_t len, uint8_t digest[32]) {
uint32_t state[8] = {
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
};
uint8_t block[64];
size_t i;
for (i = 0; i + 64 <= len; i += 64) {
sha256Transform(state, data + i);
}
size_t remaining = len - i;
memcpy(block, data + i, remaining);
block[remaining++] = 0x80;
if (remaining > 56) {
memset(block + remaining, 0, 64 - remaining);
sha256Transform(state, block);
remaining = 0;
}
memset(block + remaining, 0, 56 - remaining);
uint64_t bits = len * 8;
for (int j = 0; j < 8; j++) {
block[56 + j] = (bits >> ((7 - j) * 8)) & 0xFF;
}
sha256Transform(state, block);
for (int j = 0; j < 8; j++) {
digest[j * 4] = (state[j] >> 24) & 0xFF;
digest[j * 4 + 1] = (state[j] >> 16) & 0xFF;
digest[j * 4 + 2] = (state[j] >> 8) & 0xFF;
digest[j * 4 + 3] = state[j] & 0xFF;
}
}
static void randomCallback(uv_random_t* req, int status, void* buf, size_t len) {
WrenHandle* fiber = (WrenHandle*)req->data;
WrenVM* vm = getVM();
wrenEnsureSlots(vm, 3);
wrenSetSlotNewList(vm, 2);
if (status == 0) {
uint8_t* bytes = (uint8_t*)buf;
for (size_t i = 0; i < len; i++) {
wrenSetSlotDouble(vm, 1, (double)bytes[i]);
wrenInsertInList(vm, 2, -1, 1);
}
}
free(buf);
free(req);
schedulerResume(fiber, true);
schedulerFinishResume();
}
void cryptoRandomBytes(WrenVM* vm) {
int length = (int)wrenGetSlotDouble(vm, 1);
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);
if (length < 0 || length > 65536) {
wrenSetSlotString(vm, 0, "Length must be between 0 and 65536.");
wrenAbortFiber(vm, 0);
return;
}
uv_random_t* req = (uv_random_t*)malloc(sizeof(uv_random_t));
void* buf = malloc(length);
if (req == NULL || buf == NULL) {
if (req) free(req);
if (buf) free(buf);
wrenReleaseHandle(vm, fiber);
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
req->data = fiber;
int result = uv_random(getLoop(), req, buf, length, 0, randomCallback);
if (result != 0) {
free(req);
free(buf);
wrenReleaseHandle(vm, fiber);
wrenSetSlotString(vm, 0, uv_strerror(result));
wrenAbortFiber(vm, 0);
}
}
static void hashHelper(WrenVM* vm, void (*hashFn)(const uint8_t*, size_t, uint8_t*), int digestLen) {
int count = wrenGetListCount(vm, 1);
uint8_t* data = (uint8_t*)malloc(count);
if (data == NULL) {
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
wrenEnsureSlots(vm, 3);
for (int i = 0; i < count; i++) {
wrenGetListElement(vm, 1, i, 2);
data[i] = (uint8_t)wrenGetSlotDouble(vm, 2);
}
uint8_t* digest = (uint8_t*)malloc(digestLen);
if (digest == NULL) {
free(data);
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
hashFn(data, count, digest);
free(data);
wrenSetSlotNewList(vm, 0);
for (int i = 0; i < digestLen; i++) {
wrenSetSlotDouble(vm, 2, (double)digest[i]);
wrenInsertInList(vm, 0, -1, 2);
}
free(digest);
}
void cryptoMd5(WrenVM* vm) {
hashHelper(vm, md5, 16);
}
void cryptoSha1(WrenVM* vm) {
hashHelper(vm, sha1, 20);
}
void cryptoSha256(WrenVM* vm) {
hashHelper(vm, sha256, 32);
}
+13
View File
@@ -0,0 +1,13 @@
// retoor <retoor@molodetz.nl>
#ifndef wren_crypto_h
#define wren_crypto_h
#include "wren.h"
void cryptoRandomBytes(WrenVM* vm);
void cryptoMd5(WrenVM* vm);
void cryptoSha1(WrenVM* vm);
void cryptoSha256(WrenVM* vm);
#endif
+63
View File
@@ -0,0 +1,63 @@
// retoor <retoor@molodetz.nl>
import "scheduler" for Scheduler
class Crypto {
foreign static randomBytes_(length, fiber)
static randomBytes(length) {
if (!(length is Num)) Fiber.abort("Length must be a number.")
if (length < 0) Fiber.abort("Length must be non-negative.")
randomBytes_(length, Fiber.current)
return Scheduler.runNextScheduled_()
}
static randomInt(min, max) {
if (!(min is Num) || !(max is Num)) Fiber.abort("Arguments must be numbers.")
if (min >= max) Fiber.abort("Min must be less than max.")
var range = max - min
var bytes = randomBytes(4)
var value = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]
if (value < 0) value = -value
return min + (value % range)
}
}
class Hash {
foreign static md5_(data)
foreign static sha1_(data)
foreign static sha256_(data)
static md5(data) {
if (data is String) data = data.bytes.toList
if (!(data is List)) Fiber.abort("Data must be a String or List of bytes.")
return md5_(data)
}
static sha1(data) {
if (data is String) data = data.bytes.toList
if (!(data is List)) Fiber.abort("Data must be a String or List of bytes.")
return sha1_(data)
}
static sha256(data) {
if (data is String) data = data.bytes.toList
if (!(data is List)) Fiber.abort("Data must be a String or List of bytes.")
return sha256_(data)
}
static toHex(bytes) {
var hex = ""
for (b in bytes) {
var hi = (b >> 4) & 0x0F
var lo = b & 0x0F
hex = hex + hexDigit_(hi) + hexDigit_(lo)
}
return hex
}
static hexDigit_(n) {
if (n < 10) return String.fromCodePoint(48 + n)
return String.fromCodePoint(97 + n - 10)
}
}
+67
View File
@@ -0,0 +1,67 @@
// Please do not edit this file. It has been generated automatically
// from `src/module/crypto.wren` using `util/wren_to_c_string.py`
static const char* cryptoModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"import \"scheduler\" for Scheduler\n"
"\n"
"class Crypto {\n"
" foreign static randomBytes_(length, fiber)\n"
"\n"
" static randomBytes(length) {\n"
" if (!(length is Num)) Fiber.abort(\"Length must be a number.\")\n"
" if (length < 0) Fiber.abort(\"Length must be non-negative.\")\n"
" randomBytes_(length, Fiber.current)\n"
" return Scheduler.runNextScheduled_()\n"
" }\n"
"\n"
" static randomInt(min, max) {\n"
" if (!(min is Num) || !(max is Num)) Fiber.abort(\"Arguments must be numbers.\")\n"
" if (min >= max) Fiber.abort(\"Min must be less than max.\")\n"
" var range = max - min\n"
" var bytes = randomBytes(4)\n"
" var value = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]\n"
" if (value < 0) value = -value\n"
" return min + (value % range)\n"
" }\n"
"}\n"
"\n"
"class Hash {\n"
" foreign static md5_(data)\n"
" foreign static sha1_(data)\n"
" foreign static sha256_(data)\n"
"\n"
" static md5(data) {\n"
" if (data is String) data = data.bytes.toList\n"
" if (!(data is List)) Fiber.abort(\"Data must be a String or List of bytes.\")\n"
" return md5_(data)\n"
" }\n"
"\n"
" static sha1(data) {\n"
" if (data is String) data = data.bytes.toList\n"
" if (!(data is List)) Fiber.abort(\"Data must be a String or List of bytes.\")\n"
" return sha1_(data)\n"
" }\n"
"\n"
" static sha256(data) {\n"
" if (data is String) data = data.bytes.toList\n"
" if (!(data is List)) Fiber.abort(\"Data must be a String or List of bytes.\")\n"
" return sha256_(data)\n"
" }\n"
"\n"
" static toHex(bytes) {\n"
" var hex = \"\"\n"
" for (b in bytes) {\n"
" var hi = (b >> 4) & 0x0F\n"
" var lo = b & 0x0F\n"
" hex = hex + hexDigit_(hi) + hexDigit_(lo)\n"
" }\n"
" return hex\n"
" }\n"
"\n"
" static hexDigit_(n) {\n"
" if (n < 10) return String.fromCodePoint(48 + n)\n"
" return String.fromCodePoint(97 + n - 10)\n"
" }\n"
"}\n";
+74
View File
@@ -0,0 +1,74 @@
// retoor <retoor@molodetz.nl>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "datetime.h"
#include "wren.h"
void datetimeNow(WrenVM* vm) {
time_t now = time(NULL);
wrenSetSlotDouble(vm, 0, (double)now);
}
void datetimeFromTimestamp(WrenVM* vm) {
double timestamp = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, timestamp);
}
void datetimeFormat(WrenVM* vm) {
double timestamp = wrenGetSlotDouble(vm, 1);
const char* pattern = wrenGetSlotString(vm, 2);
if (pattern == NULL) {
wrenSetSlotString(vm, 0, "");
return;
}
time_t t = (time_t)timestamp;
struct tm* tm_info = localtime(&t);
if (tm_info == NULL) {
wrenSetSlotString(vm, 0, "");
return;
}
char buffer[256];
size_t result = strftime(buffer, sizeof(buffer), pattern, tm_info);
if (result == 0) {
wrenSetSlotString(vm, 0, "");
return;
}
wrenSetSlotString(vm, 0, buffer);
}
void datetimeComponent(WrenVM* vm) {
double timestamp = wrenGetSlotDouble(vm, 1);
int index = (int)wrenGetSlotDouble(vm, 2);
time_t t = (time_t)timestamp;
struct tm* tm_info = localtime(&t);
if (tm_info == NULL) {
wrenSetSlotDouble(vm, 0, 0);
return;
}
int result = 0;
switch (index) {
case 0: result = tm_info->tm_year + 1900; break;
case 1: result = tm_info->tm_mon + 1; break;
case 2: result = tm_info->tm_mday; break;
case 3: result = tm_info->tm_hour; break;
case 4: result = tm_info->tm_min; break;
case 5: result = tm_info->tm_sec; break;
case 6: result = tm_info->tm_wday; break;
case 7: result = tm_info->tm_yday + 1; break;
case 8: result = tm_info->tm_isdst; break;
default: result = 0; break;
}
wrenSetSlotDouble(vm, 0, (double)result);
}
+13
View File
@@ -0,0 +1,13 @@
// retoor <retoor@molodetz.nl>
#ifndef wren_datetime_h
#define wren_datetime_h
#include "wren.h"
void datetimeNow(WrenVM* vm);
void datetimeFromTimestamp(WrenVM* vm);
void datetimeFormat(WrenVM* vm);
void datetimeComponent(WrenVM* vm);
#endif
+113
View File
@@ -0,0 +1,113 @@
// retoor <retoor@molodetz.nl>
class DateTime {
foreign static now_()
foreign static fromTimestamp_(timestamp)
foreign static format_(timestamp, pattern)
construct now() {
_timestamp = DateTime.now_()
}
construct fromTimestamp(timestamp) {
_timestamp = timestamp
}
static parse(string, format) {
Fiber.abort("DateTime.parse not implemented yet")
}
timestamp { _timestamp }
year { DateTime.component_(_timestamp, 0) }
month { DateTime.component_(_timestamp, 1) }
day { DateTime.component_(_timestamp, 2) }
hour { DateTime.component_(_timestamp, 3) }
minute { DateTime.component_(_timestamp, 4) }
second { DateTime.component_(_timestamp, 5) }
dayOfWeek { DateTime.component_(_timestamp, 6) }
dayOfYear { DateTime.component_(_timestamp, 7) }
isDst { DateTime.component_(_timestamp, 8) == 1 }
foreign static component_(timestamp, index)
format(pattern) { DateTime.format_(_timestamp, pattern) }
toIso8601 { format("\%Y-\%m-\%dT\%H:\%M:\%S") }
toString { toIso8601 }
+(duration) {
if (!(duration is Duration)) Fiber.abort("Expected Duration.")
return DateTime.fromTimestamp(_timestamp + duration.seconds)
}
-(other) {
if (other is DateTime) {
return Duration.fromSeconds(_timestamp - other.timestamp)
}
if (other is Duration) {
return DateTime.fromTimestamp(_timestamp - other.seconds)
}
Fiber.abort("Expected DateTime or Duration.")
}
==(other) {
if (!(other is DateTime)) return false
return _timestamp == other.timestamp
}
<(other) { _timestamp < other.timestamp }
>(other) { _timestamp > other.timestamp }
<=(other) { _timestamp <= other.timestamp }
>=(other) { _timestamp >= other.timestamp }
}
class Duration {
construct fromMilliseconds(ms) {
_ms = ms
}
construct fromSeconds(s) {
_ms = s * 1000
}
construct fromMinutes(m) {
_ms = m * 60 * 1000
}
construct fromHours(h) {
_ms = h * 60 * 60 * 1000
}
construct fromDays(d) {
_ms = d * 24 * 60 * 60 * 1000
}
milliseconds { _ms }
seconds { _ms / 1000 }
minutes { _ms / 60000 }
hours { _ms / 3600000 }
days { _ms / 86400000 }
+(other) {
if (!(other is Duration)) Fiber.abort("Expected Duration.")
return Duration.fromMilliseconds(_ms + other.milliseconds)
}
-(other) {
if (!(other is Duration)) Fiber.abort("Expected Duration.")
return Duration.fromMilliseconds(_ms - other.milliseconds)
}
*(factor) {
return Duration.fromMilliseconds(_ms * factor)
}
==(other) {
if (!(other is Duration)) return false
return _ms == other.milliseconds
}
toString { "%(days.floor)d %(hours.floor % 24)h %(minutes.floor % 60)m %(seconds.floor % 60)s" }
}
+117
View File
@@ -0,0 +1,117 @@
// Please do not edit this file. It has been generated automatically
// from `src/module/datetime.wren` using `util/wren_to_c_string.py`
static const char* datetimeModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"class DateTime {\n"
" foreign static now_()\n"
" foreign static fromTimestamp_(timestamp)\n"
" foreign static format_(timestamp, pattern)\n"
"\n"
" construct now() {\n"
" _timestamp = DateTime.now_()\n"
" }\n"
"\n"
" construct fromTimestamp(timestamp) {\n"
" _timestamp = timestamp\n"
" }\n"
"\n"
" static parse(string, format) {\n"
" Fiber.abort(\"DateTime.parse not implemented yet\")\n"
" }\n"
"\n"
" timestamp { _timestamp }\n"
"\n"
" year { DateTime.component_(_timestamp, 0) }\n"
" month { DateTime.component_(_timestamp, 1) }\n"
" day { DateTime.component_(_timestamp, 2) }\n"
" hour { DateTime.component_(_timestamp, 3) }\n"
" minute { DateTime.component_(_timestamp, 4) }\n"
" second { DateTime.component_(_timestamp, 5) }\n"
" dayOfWeek { DateTime.component_(_timestamp, 6) }\n"
" dayOfYear { DateTime.component_(_timestamp, 7) }\n"
" isDst { DateTime.component_(_timestamp, 8) == 1 }\n"
"\n"
" foreign static component_(timestamp, index)\n"
"\n"
" format(pattern) { DateTime.format_(_timestamp, pattern) }\n"
"\n"
" toIso8601 { format(\"\\%Y-\\%m-\\%dT\\%H:\\%M:\\%S\") }\n"
"\n"
" toString { toIso8601 }\n"
"\n"
" +(duration) {\n"
" if (!(duration is Duration)) Fiber.abort(\"Expected Duration.\")\n"
" return DateTime.fromTimestamp(_timestamp + duration.seconds)\n"
" }\n"
"\n"
" -(other) {\n"
" if (other is DateTime) {\n"
" return Duration.fromSeconds(_timestamp - other.timestamp)\n"
" }\n"
" if (other is Duration) {\n"
" return DateTime.fromTimestamp(_timestamp - other.seconds)\n"
" }\n"
" Fiber.abort(\"Expected DateTime or Duration.\")\n"
" }\n"
"\n"
" ==(other) {\n"
" if (!(other is DateTime)) return false\n"
" return _timestamp == other.timestamp\n"
" }\n"
"\n"
" <(other) { _timestamp < other.timestamp }\n"
" >(other) { _timestamp > other.timestamp }\n"
" <=(other) { _timestamp <= other.timestamp }\n"
" >=(other) { _timestamp >= other.timestamp }\n"
"}\n"
"\n"
"class Duration {\n"
" construct fromMilliseconds(ms) {\n"
" _ms = ms\n"
" }\n"
"\n"
" construct fromSeconds(s) {\n"
" _ms = s * 1000\n"
" }\n"
"\n"
" construct fromMinutes(m) {\n"
" _ms = m * 60 * 1000\n"
" }\n"
"\n"
" construct fromHours(h) {\n"
" _ms = h * 60 * 60 * 1000\n"
" }\n"
"\n"
" construct fromDays(d) {\n"
" _ms = d * 24 * 60 * 60 * 1000\n"
" }\n"
"\n"
" milliseconds { _ms }\n"
" seconds { _ms / 1000 }\n"
" minutes { _ms / 60000 }\n"
" hours { _ms / 3600000 }\n"
" days { _ms / 86400000 }\n"
"\n"
" +(other) {\n"
" if (!(other is Duration)) Fiber.abort(\"Expected Duration.\")\n"
" return Duration.fromMilliseconds(_ms + other.milliseconds)\n"
" }\n"
"\n"
" -(other) {\n"
" if (!(other is Duration)) Fiber.abort(\"Expected Duration.\")\n"
" return Duration.fromMilliseconds(_ms - other.milliseconds)\n"
" }\n"
"\n"
" *(factor) {\n"
" return Duration.fromMilliseconds(_ms * factor)\n"
" }\n"
"\n"
" ==(other) {\n"
" if (!(other is Duration)) return false\n"
" return _ms == other.milliseconds\n"
" }\n"
"\n"
" toString { \"%(days.floor)d %(hours.floor % 24)h %(minutes.floor % 60)m %(seconds.floor % 60)s\" }\n"
"}\n";
+83
View File
@@ -0,0 +1,83 @@
// retoor <retoor@molodetz.nl>
#include <stdlib.h>
#include <string.h>
#include "dns.h"
#include "scheduler.h"
#include "wren.h"
#include "vm.h"
#include "uv.h"
static void dnsCallback(uv_getaddrinfo_t* req, int status, struct addrinfo* res) {
WrenHandle* fiber = (WrenHandle*)req->data;
WrenVM* vm = getVM();
wrenEnsureSlots(vm, 3);
wrenSetSlotNewList(vm, 2);
if (status == 0 && res != NULL) {
struct addrinfo* addr = res;
while (addr != NULL) {
char ip[64];
if (addr->ai_family == AF_INET) {
struct sockaddr_in* sa = (struct sockaddr_in*)addr->ai_addr;
uv_ip4_name(sa, ip, sizeof(ip));
wrenSetSlotString(vm, 1, ip);
wrenInsertInList(vm, 2, -1, 1);
} else if (addr->ai_family == AF_INET6) {
struct sockaddr_in6* sa = (struct sockaddr_in6*)addr->ai_addr;
uv_ip6_name(sa, ip, sizeof(ip));
wrenSetSlotString(vm, 1, ip);
wrenInsertInList(vm, 2, -1, 1);
}
addr = addr->ai_next;
}
uv_freeaddrinfo(res);
}
free(req);
schedulerResume(fiber, true);
schedulerFinishResume();
}
void dnsLookup(WrenVM* vm) {
const char* hostname = wrenGetSlotString(vm, 1);
int family = (int)wrenGetSlotDouble(vm, 2);
WrenHandle* fiber = wrenGetSlotHandle(vm, 3);
if (hostname == NULL) {
wrenSetSlotString(vm, 0, "Hostname required.");
wrenAbortFiber(vm, 0);
return;
}
uv_getaddrinfo_t* req = (uv_getaddrinfo_t*)malloc(sizeof(uv_getaddrinfo_t));
if (req == NULL) {
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
req->data = fiber;
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_STREAM;
if (family == 4) {
hints.ai_family = AF_INET;
} else if (family == 6) {
hints.ai_family = AF_INET6;
} else {
hints.ai_family = AF_UNSPEC;
}
int result = uv_getaddrinfo(getLoop(), req, dnsCallback, hostname, NULL, &hints);
if (result != 0) {
free(req);
wrenReleaseHandle(vm, fiber);
wrenSetSlotString(vm, 0, uv_strerror(result));
wrenAbortFiber(vm, 0);
return;
}
}
+10
View File
@@ -0,0 +1,10 @@
// retoor <retoor@molodetz.nl>
#ifndef wren_dns_h
#define wren_dns_h
#include "wren.h"
void dnsLookup(WrenVM* vm);
#endif
+17
View File
@@ -0,0 +1,17 @@
// retoor <retoor@molodetz.nl>
import "scheduler" for Scheduler
class Dns {
static lookup(hostname) { lookup(hostname, 0) }
static lookup(hostname, family) {
if (!(hostname is String)) Fiber.abort("Hostname must be a string.")
if (family != 0 && family != 4 && family != 6) {
Fiber.abort("Family must be 0 (any), 4 (IPv4), or 6 (IPv6).")
}
return Scheduler.await_ { lookup_(hostname, family, Fiber.current) }
}
foreign static lookup_(hostname, family, fiber)
}
+21
View File
@@ -0,0 +1,21 @@
// Please do not edit this file. It has been generated automatically
// from `src/module/dns.wren` using `util/wren_to_c_string.py`
static const char* dnsModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"import \"scheduler\" for Scheduler\n"
"\n"
"class Dns {\n"
" static lookup(hostname) { lookup(hostname, 0) }\n"
"\n"
" static lookup(hostname, family) {\n"
" if (!(hostname is String)) Fiber.abort(\"Hostname must be a string.\")\n"
" if (family != 0 && family != 4 && family != 6) {\n"
" Fiber.abort(\"Family must be 0 (any), 4 (IPv4), or 6 (IPv6).\")\n"
" }\n"
" return Scheduler.await_ { lookup_(hostname, family, Fiber.current) }\n"
" }\n"
"\n"
" foreign static lookup_(hostname, family, fiber)\n"
"}\n";
+108
View File
@@ -0,0 +1,108 @@
// retoor <retoor@molodetz.nl>
#include <stdlib.h>
#include <string.h>
#include "env.h"
#include "wren.h"
#include "uv.h"
void envGet(WrenVM* vm) {
const char* name = wrenGetSlotString(vm, 1);
if (name == NULL) {
wrenSetSlotNull(vm, 0);
return;
}
char buffer[1024];
size_t size = sizeof(buffer);
int result = uv_os_getenv(name, buffer, &size);
if (result == UV_ENOENT) {
wrenSetSlotNull(vm, 0);
return;
}
if (result == UV_ENOBUFS) {
char* largeBuffer = (char*)malloc(size);
if (largeBuffer == NULL) {
wrenSetSlotNull(vm, 0);
return;
}
result = uv_os_getenv(name, largeBuffer, &size);
if (result == 0) {
wrenSetSlotString(vm, 0, largeBuffer);
} else {
wrenSetSlotNull(vm, 0);
}
free(largeBuffer);
return;
}
if (result != 0) {
wrenSetSlotNull(vm, 0);
return;
}
wrenSetSlotString(vm, 0, buffer);
}
void envSet(WrenVM* vm) {
const char* name = wrenGetSlotString(vm, 1);
const char* value = wrenGetSlotString(vm, 2);
if (name == NULL || value == NULL) {
wrenSetSlotString(vm, 0, "Name and value must be strings.");
wrenAbortFiber(vm, 0);
return;
}
int result = uv_os_setenv(name, value);
if (result != 0) {
wrenSetSlotString(vm, 0, "Failed to set environment variable.");
wrenAbortFiber(vm, 0);
return;
}
wrenSetSlotNull(vm, 0);
}
void envDelete(WrenVM* vm) {
const char* name = wrenGetSlotString(vm, 1);
if (name == NULL) {
wrenSetSlotString(vm, 0, "Name must be a string.");
wrenAbortFiber(vm, 0);
return;
}
int result = uv_os_unsetenv(name);
if (result != 0) {
wrenSetSlotString(vm, 0, "Failed to delete environment variable.");
wrenAbortFiber(vm, 0);
return;
}
wrenSetSlotNull(vm, 0);
}
void envAll(WrenVM* vm) {
uv_env_item_t* envItems = NULL;
int count = 0;
int result = uv_os_environ(&envItems, &count);
if (result != 0) {
wrenSetSlotNewMap(vm, 0);
return;
}
wrenEnsureSlots(vm, 3);
wrenSetSlotNewMap(vm, 0);
for (int i = 0; i < count; i++) {
wrenSetSlotString(vm, 1, envItems[i].name);
wrenSetSlotString(vm, 2, envItems[i].value);
wrenSetMapValue(vm, 0, 1, 2);
}
uv_os_free_environ(envItems, count);
}
+13
View File
@@ -0,0 +1,13 @@
// retoor <retoor@molodetz.nl>
#ifndef wren_env_h
#define wren_env_h
#include "wren.h"
void envGet(WrenVM* vm);
void envSet(WrenVM* vm);
void envDelete(WrenVM* vm);
void envAll(WrenVM* vm);
#endif
+8
View File
@@ -0,0 +1,8 @@
// retoor <retoor@molodetz.nl>
class Environment {
foreign static get(name)
foreign static set(name, value)
foreign static delete(name)
foreign static all
}
+12
View File
@@ -0,0 +1,12 @@
// Please do not edit this file. It has been generated automatically
// from `src/module/env.wren` using `util/wren_to_c_string.py`
static const char* envModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"class Environment {\n"
" foreign static get(name)\n"
" foreign static set(name, value)\n"
" foreign static delete(name)\n"
" foreign static all\n"
"}\n";
+304
View File
@@ -0,0 +1,304 @@
// retoor <retoor@molodetz.nl>
import "net" for Socket
import "tls" for TlsSocket
import "dns" for Dns
import "json" for Json
import "base64" for Base64
class Url {
construct parse(url) {
_scheme = "http"
_host = ""
_port = 80
_path = "/"
_query = ""
var rest = url
var schemeEnd = rest.indexOf("://")
if (schemeEnd >= 0) {
_scheme = Url.toLower_(rest[0...schemeEnd])
rest = rest[(schemeEnd + 3)..-1]
if (_scheme == "https") _port = 443
}
var pathStart = rest.indexOf("/")
var hostPart = pathStart >= 0 ? rest[0...pathStart] : rest
var pathPart = pathStart >= 0 ? rest[pathStart..-1] : "/"
var queryStart = pathPart.indexOf("?")
if (queryStart >= 0) {
_query = pathPart[(queryStart + 1)..-1]
_path = pathPart[0...queryStart]
} else {
_path = pathPart
}
var portStart = hostPart.indexOf(":")
if (portStart >= 0) {
_host = hostPart[0...portStart]
_port = Num.fromString(hostPart[(portStart + 1)..-1])
} else {
_host = hostPart
}
}
static toLower_(str) {
var result = ""
for (c in str) {
var cp = c.codePoints[0]
if (cp >= 65 && cp <= 90) {
result = result + String.fromCodePoint(cp + 32)
} else {
result = result + c
}
}
return result
}
scheme { _scheme }
host { _host }
port { _port }
path { _path }
query { _query }
fullPath {
if (_query.count > 0) return _path + "?" + _query
return _path
}
}
class HttpResponse {
construct new_(statusCode, statusText, headers, body) {
_statusCode = statusCode
_statusText = statusText
_headers = headers
_body = body
}
statusCode { _statusCode }
statusText { _statusText }
headers { _headers }
body { _body }
ok { _statusCode >= 200 && _statusCode < 300 }
header(name) {
var lower = Url.toLower_(name)
for (entry in _headers) {
if (Url.toLower_(entry.key) == lower) return entry.value
}
return null
}
json {
if (_body == null || _body.count == 0) return null
return Json.parse(_body)
}
toString { "HttpResponse(%(statusCode) %(statusText))" }
}
class Http {
static get(url) { request(url, "GET", null, {}) }
static get(url, headers) { request(url, "GET", null, headers) }
static post(url, body) { request(url, "POST", body, {}) }
static post(url, body, headers) { request(url, "POST", body, headers) }
static put(url, body) { request(url, "PUT", body, {}) }
static put(url, body, headers) { request(url, "PUT", body, headers) }
static delete(url) { request(url, "DELETE", null, {}) }
static delete(url, headers) { request(url, "DELETE", null, headers) }
static patch(url, body) { request(url, "PATCH", body, {}) }
static patch(url, body, headers) { request(url, "PATCH", body, headers) }
static request(url, method, body, headers) {
var parsed = Url.parse(url)
if (parsed.scheme != "http" && parsed.scheme != "https") {
Fiber.abort("Unsupported scheme: %(parsed.scheme)")
}
var isHttps = parsed.scheme == "https"
var bodyStr = ""
if (body != null) {
if (body is String) {
bodyStr = body
} else if (body is Map || body is List) {
bodyStr = Json.stringify(body)
if (!hasHeader_(headers, "Content-Type")) {
headers["Content-Type"] = "application/json"
}
} else {
bodyStr = body.toString
}
}
var requestHeaders = {}
requestHeaders["Host"] = parsed.host
if ((isHttps && parsed.port != 443) || (!isHttps && parsed.port != 80)) {
requestHeaders["Host"] = "%(parsed.host):%(parsed.port)"
}
requestHeaders["Connection"] = "close"
requestHeaders["User-Agent"] = "Wren-CLI/1.0"
if (bodyStr.count > 0) {
requestHeaders["Content-Length"] = bodyStr.count.toString
}
for (entry in headers) {
requestHeaders[entry.key] = entry.value
}
var request = "%(method) %(parsed.fullPath) HTTP/1.1\r\n"
for (entry in requestHeaders) {
request = request + "%(entry.key): %(entry.value)\r\n"
}
request = request + "\r\n" + bodyStr
var addresses = Dns.lookup(parsed.host, 4)
if (addresses.count == 0) {
Fiber.abort("Could not resolve host: %(parsed.host)")
}
var socket
if (isHttps) {
socket = TlsSocket.connect(addresses[0], parsed.port, parsed.host)
} else {
socket = Socket.connect(addresses[0], parsed.port)
}
socket.write(request)
var response = ""
while (true) {
var chunk = socket.read()
if (chunk == null || chunk.count == 0) break
response = response + chunk
}
socket.close()
return parseResponse_(response)
}
static hasHeader_(headers, name) {
var lower = Url.toLower_(name)
for (entry in headers) {
if (Url.toLower_(entry.key) == lower) return true
}
return false
}
static parseResponse_(response) {
var headerEnd = response.indexOf("\r\n\r\n")
if (headerEnd < 0) {
Fiber.abort("Invalid HTTP response: no header/body separator")
}
var headerPart = response[0...headerEnd]
var body = response[(headerEnd + 4)..-1]
var lines = splitLines_(headerPart)
if (lines.count == 0) {
Fiber.abort("Invalid HTTP response: empty headers")
}
var statusLine = lines[0]
var statusParts = statusLine.split(" ")
if (statusParts.count < 2) {
Fiber.abort("Invalid HTTP status line")
}
var statusCode = Num.fromString(statusParts[1])
var statusText = statusParts.count > 2 ? statusParts[2..-1].join(" ") : ""
var headers = {}
for (i in 1...lines.count) {
var line = lines[i]
var colonPos = line.indexOf(":")
if (colonPos > 0) {
var name = line[0...colonPos].trim()
var value = line[(colonPos + 1)..-1].trim()
headers[name] = value
}
}
var transferEncoding = null
for (entry in headers) {
if (Url.toLower_(entry.key) == "transfer-encoding") {
transferEncoding = Url.toLower_(entry.value)
break
}
}
if (transferEncoding == "chunked") {
body = decodeChunked_(body)
}
return HttpResponse.new_(statusCode, statusText, headers, body)
}
static splitLines_(text) {
var lines = []
var start = 0
var i = 0
while (i < text.count) {
if (text[i] == "\r" && i + 1 < text.count && text[i + 1] == "\n") {
lines.add(text[start...i])
start = i + 2
i = i + 2
} else {
i = i + 1
}
}
if (start < text.count) {
lines.add(text[start..-1])
}
return lines
}
static decodeChunked_(body) {
var result = ""
var pos = 0
while (pos < body.count) {
var lineEnd = body.indexOf("\r\n", pos)
if (lineEnd < 0) break
var sizeStr = body[pos...lineEnd].trim()
var size = parseHex_(sizeStr)
if (size == 0) break
pos = lineEnd + 2
if (pos + size > body.count) break
result = result + body[pos...(pos + size)]
pos = pos + size + 2
}
return result
}
static parseHex_(str) {
var result = 0
for (c in str) {
var cp = c.codePoints[0]
var digit = 0
if (cp >= 48 && cp <= 57) {
digit = cp - 48
} else if (cp >= 65 && cp <= 70) {
digit = cp - 55
} else if (cp >= 97 && cp <= 102) {
digit = cp - 87
} else {
break
}
result = result * 16 + digit
}
return result
}
}
+308
View File
@@ -0,0 +1,308 @@
// Please do not edit this file. It has been generated automatically
// from `/home/retoor/projects/wren-cli/src/module/http.wren` using `util/wren_to_c_string.py`
static const char* httpModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"import \"net\" for Socket\n"
"import \"tls\" for TlsSocket\n"
"import \"dns\" for Dns\n"
"import \"json\" for Json\n"
"import \"base64\" for Base64\n"
"\n"
"class Url {\n"
" construct parse(url) {\n"
" _scheme = \"http\"\n"
" _host = \"\"\n"
" _port = 80\n"
" _path = \"/\"\n"
" _query = \"\"\n"
"\n"
" var rest = url\n"
"\n"
" var schemeEnd = rest.indexOf(\"://\")\n"
" if (schemeEnd >= 0) {\n"
" _scheme = Url.toLower_(rest[0...schemeEnd])\n"
" rest = rest[(schemeEnd + 3)..-1]\n"
" if (_scheme == \"https\") _port = 443\n"
" }\n"
"\n"
" var pathStart = rest.indexOf(\"/\")\n"
" var hostPart = pathStart >= 0 ? rest[0...pathStart] : rest\n"
" var pathPart = pathStart >= 0 ? rest[pathStart..-1] : \"/\"\n"
"\n"
" var queryStart = pathPart.indexOf(\"?\")\n"
" if (queryStart >= 0) {\n"
" _query = pathPart[(queryStart + 1)..-1]\n"
" _path = pathPart[0...queryStart]\n"
" } else {\n"
" _path = pathPart\n"
" }\n"
"\n"
" var portStart = hostPart.indexOf(\":\")\n"
" if (portStart >= 0) {\n"
" _host = hostPart[0...portStart]\n"
" _port = Num.fromString(hostPart[(portStart + 1)..-1])\n"
" } else {\n"
" _host = hostPart\n"
" }\n"
" }\n"
"\n"
" static toLower_(str) {\n"
" var result = \"\"\n"
" for (c in str) {\n"
" var cp = c.codePoints[0]\n"
" if (cp >= 65 && cp <= 90) {\n"
" result = result + String.fromCodePoint(cp + 32)\n"
" } else {\n"
" result = result + c\n"
" }\n"
" }\n"
" return result\n"
" }\n"
"\n"
" scheme { _scheme }\n"
" host { _host }\n"
" port { _port }\n"
" path { _path }\n"
" query { _query }\n"
"\n"
" fullPath {\n"
" if (_query.count > 0) return _path + \"?\" + _query\n"
" return _path\n"
" }\n"
"}\n"
"\n"
"class HttpResponse {\n"
" construct new_(statusCode, statusText, headers, body) {\n"
" _statusCode = statusCode\n"
" _statusText = statusText\n"
" _headers = headers\n"
" _body = body\n"
" }\n"
"\n"
" statusCode { _statusCode }\n"
" statusText { _statusText }\n"
" headers { _headers }\n"
" body { _body }\n"
" ok { _statusCode >= 200 && _statusCode < 300 }\n"
"\n"
" header(name) {\n"
" var lower = Url.toLower_(name)\n"
" for (entry in _headers) {\n"
" if (Url.toLower_(entry.key) == lower) return entry.value\n"
" }\n"
" return null\n"
" }\n"
"\n"
" json {\n"
" if (_body == null || _body.count == 0) return null\n"
" return Json.parse(_body)\n"
" }\n"
"\n"
" toString { \"HttpResponse(%(statusCode) %(statusText))\" }\n"
"}\n"
"\n"
"class Http {\n"
" static get(url) { request(url, \"GET\", null, {}) }\n"
" static get(url, headers) { request(url, \"GET\", null, headers) }\n"
"\n"
" static post(url, body) { request(url, \"POST\", body, {}) }\n"
" static post(url, body, headers) { request(url, \"POST\", body, headers) }\n"
"\n"
" static put(url, body) { request(url, \"PUT\", body, {}) }\n"
" static put(url, body, headers) { request(url, \"PUT\", body, headers) }\n"
"\n"
" static delete(url) { request(url, \"DELETE\", null, {}) }\n"
" static delete(url, headers) { request(url, \"DELETE\", null, headers) }\n"
"\n"
" static patch(url, body) { request(url, \"PATCH\", body, {}) }\n"
" static patch(url, body, headers) { request(url, \"PATCH\", body, headers) }\n"
"\n"
" static request(url, method, body, headers) {\n"
" var parsed = Url.parse(url)\n"
"\n"
" if (parsed.scheme != \"http\" && parsed.scheme != \"https\") {\n"
" Fiber.abort(\"Unsupported scheme: %(parsed.scheme)\")\n"
" }\n"
"\n"
" var isHttps = parsed.scheme == \"https\"\n"
"\n"
" var bodyStr = \"\"\n"
" if (body != null) {\n"
" if (body is String) {\n"
" bodyStr = body\n"
" } else if (body is Map || body is List) {\n"
" bodyStr = Json.stringify(body)\n"
" if (!hasHeader_(headers, \"Content-Type\")) {\n"
" headers[\"Content-Type\"] = \"application/json\"\n"
" }\n"
" } else {\n"
" bodyStr = body.toString\n"
" }\n"
" }\n"
"\n"
" var requestHeaders = {}\n"
" requestHeaders[\"Host\"] = parsed.host\n"
" if ((isHttps && parsed.port != 443) || (!isHttps && parsed.port != 80)) {\n"
" requestHeaders[\"Host\"] = \"%(parsed.host):%(parsed.port)\"\n"
" }\n"
" requestHeaders[\"Connection\"] = \"close\"\n"
" requestHeaders[\"User-Agent\"] = \"Wren-CLI/1.0\"\n"
"\n"
" if (bodyStr.count > 0) {\n"
" requestHeaders[\"Content-Length\"] = bodyStr.count.toString\n"
" }\n"
"\n"
" for (entry in headers) {\n"
" requestHeaders[entry.key] = entry.value\n"
" }\n"
"\n"
" var request = \"%(method) %(parsed.fullPath) HTTP/1.1\\r\\n\"\n"
" for (entry in requestHeaders) {\n"
" request = request + \"%(entry.key): %(entry.value)\\r\\n\"\n"
" }\n"
" request = request + \"\\r\\n\" + bodyStr\n"
"\n"
" var addresses = Dns.lookup(parsed.host, 4)\n"
" if (addresses.count == 0) {\n"
" Fiber.abort(\"Could not resolve host: %(parsed.host)\")\n"
" }\n"
"\n"
" var socket\n"
" if (isHttps) {\n"
" socket = TlsSocket.connect(addresses[0], parsed.port, parsed.host)\n"
" } else {\n"
" socket = Socket.connect(addresses[0], parsed.port)\n"
" }\n"
" socket.write(request)\n"
"\n"
" var response = \"\"\n"
" while (true) {\n"
" var chunk = socket.read()\n"
" if (chunk == null || chunk.count == 0) break\n"
" response = response + chunk\n"
" }\n"
" socket.close()\n"
"\n"
" return parseResponse_(response)\n"
" }\n"
"\n"
" static hasHeader_(headers, name) {\n"
" var lower = Url.toLower_(name)\n"
" for (entry in headers) {\n"
" if (Url.toLower_(entry.key) == lower) return true\n"
" }\n"
" return false\n"
" }\n"
"\n"
" static parseResponse_(response) {\n"
" var headerEnd = response.indexOf(\"\\r\\n\\r\\n\")\n"
" if (headerEnd < 0) {\n"
" Fiber.abort(\"Invalid HTTP response: no header/body separator\")\n"
" }\n"
"\n"
" var headerPart = response[0...headerEnd]\n"
" var body = response[(headerEnd + 4)..-1]\n"
"\n"
" var lines = splitLines_(headerPart)\n"
" if (lines.count == 0) {\n"
" Fiber.abort(\"Invalid HTTP response: empty headers\")\n"
" }\n"
"\n"
" var statusLine = lines[0]\n"
" var statusParts = statusLine.split(\" \")\n"
" if (statusParts.count < 2) {\n"
" Fiber.abort(\"Invalid HTTP status line\")\n"
" }\n"
"\n"
" var statusCode = Num.fromString(statusParts[1])\n"
" var statusText = statusParts.count > 2 ? statusParts[2..-1].join(\" \") : \"\"\n"
"\n"
" var headers = {}\n"
" for (i in 1...lines.count) {\n"
" var line = lines[i]\n"
" var colonPos = line.indexOf(\":\")\n"
" if (colonPos > 0) {\n"
" var name = line[0...colonPos].trim()\n"
" var value = line[(colonPos + 1)..-1].trim()\n"
" headers[name] = value\n"
" }\n"
" }\n"
"\n"
" var transferEncoding = null\n"
" for (entry in headers) {\n"
" if (Url.toLower_(entry.key) == \"transfer-encoding\") {\n"
" transferEncoding = Url.toLower_(entry.value)\n"
" break\n"
" }\n"
" }\n"
"\n"
" if (transferEncoding == \"chunked\") {\n"
" body = decodeChunked_(body)\n"
" }\n"
"\n"
" return HttpResponse.new_(statusCode, statusText, headers, body)\n"
" }\n"
"\n"
" static splitLines_(text) {\n"
" var lines = []\n"
" var start = 0\n"
" var i = 0\n"
" while (i < text.count) {\n"
" if (text[i] == \"\\r\" && i + 1 < text.count && text[i + 1] == \"\\n\") {\n"
" lines.add(text[start...i])\n"
" start = i + 2\n"
" i = i + 2\n"
" } else {\n"
" i = i + 1\n"
" }\n"
" }\n"
" if (start < text.count) {\n"
" lines.add(text[start..-1])\n"
" }\n"
" return lines\n"
" }\n"
"\n"
" static decodeChunked_(body) {\n"
" var result = \"\"\n"
" var pos = 0\n"
"\n"
" while (pos < body.count) {\n"
" var lineEnd = body.indexOf(\"\\r\\n\", pos)\n"
" if (lineEnd < 0) break\n"
"\n"
" var sizeStr = body[pos...lineEnd].trim()\n"
" var size = parseHex_(sizeStr)\n"
"\n"
" if (size == 0) break\n"
"\n"
" pos = lineEnd + 2\n"
" if (pos + size > body.count) break\n"
"\n"
" result = result + body[pos...(pos + size)]\n"
" pos = pos + size + 2\n"
" }\n"
"\n"
" return result\n"
" }\n"
"\n"
" static parseHex_(str) {\n"
" var result = 0\n"
" for (c in str) {\n"
" var cp = c.codePoints[0]\n"
" var digit = 0\n"
" if (cp >= 48 && cp <= 57) {\n"
" digit = cp - 48\n"
" } else if (cp >= 65 && cp <= 70) {\n"
" digit = cp - 55\n"
" } else if (cp >= 97 && cp <= 102) {\n"
" digit = cp - 87\n"
" } else {\n"
" break\n"
" }\n"
" result = result * 16 + digit\n"
" }\n"
" return result\n"
" }\n"
"}\n";
+2649
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+98
View File
@@ -0,0 +1,98 @@
// retoor <retoor@molodetz.nl>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "json.h"
#include "wren.h"
#include "cJSON.h"
static void cjsonToWren(WrenVM* vm, int slot, cJSON* item) {
if (item == NULL) {
wrenSetSlotNull(vm, slot);
return;
}
switch (item->type & 0xFF) {
case cJSON_NULL:
wrenSetSlotNull(vm, slot);
break;
case cJSON_False:
wrenSetSlotBool(vm, slot, false);
break;
case cJSON_True:
wrenSetSlotBool(vm, slot, true);
break;
case cJSON_Number:
wrenSetSlotDouble(vm, slot, item->valuedouble);
break;
case cJSON_String:
wrenSetSlotString(vm, slot, item->valuestring ? item->valuestring : "");
break;
case cJSON_Array: {
wrenSetSlotNewList(vm, slot);
int elemSlot = slot + 1;
wrenEnsureSlots(vm, elemSlot + 1);
cJSON* child = item->child;
while (child != NULL) {
cjsonToWren(vm, elemSlot, child);
wrenInsertInList(vm, slot, -1, elemSlot);
child = child->next;
}
break;
}
case cJSON_Object: {
wrenSetSlotNewMap(vm, slot);
int keySlot = slot + 1;
int valueSlot = slot + 2;
wrenEnsureSlots(vm, valueSlot + 1);
cJSON* child = item->child;
while (child != NULL) {
wrenSetSlotString(vm, keySlot, child->string ? child->string : "");
cjsonToWren(vm, valueSlot, child);
wrenSetMapValue(vm, slot, keySlot, valueSlot);
child = child->next;
}
break;
}
default:
wrenSetSlotNull(vm, slot);
break;
}
}
void jsonParse(WrenVM* vm) {
const char* jsonString = wrenGetSlotString(vm, 1);
if (jsonString == NULL) {
wrenSetSlotString(vm, 0, "Expected string argument.");
wrenAbortFiber(vm, 0);
return;
}
cJSON* json = cJSON_Parse(jsonString);
if (json == NULL) {
const char* error = cJSON_GetErrorPtr();
if (error != NULL) {
char msg[256];
snprintf(msg, sizeof(msg), "JSON parse error near: %.50s", error);
wrenSetSlotString(vm, 0, msg);
} else {
wrenSetSlotString(vm, 0, "JSON parse error.");
}
wrenAbortFiber(vm, 0);
return;
}
wrenEnsureSlots(vm, 4);
cjsonToWren(vm, 0, json);
cJSON_Delete(json);
}
+10
View File
@@ -0,0 +1,10 @@
// retoor <retoor@molodetz.nl>
#ifndef wren_json_h
#define wren_json_h
#include "wren.h"
void jsonParse(WrenVM* vm);
#endif
+99
View File
@@ -0,0 +1,99 @@
// retoor <retoor@molodetz.nl>
class Json {
foreign static parse(string)
static stringify(value) { stringify_(value, "") }
static stringify(value, indent) {
var indentStr = ""
if (indent is Num && indent > 0) {
for (i in 0...indent) {
indentStr = indentStr + " "
}
} else if (indent is String) {
indentStr = indent
}
return stringify_(value, indentStr)
}
static stringify_(value, indent) {
return stringifyValue_(value, indent, "")
}
static stringifyValue_(value, indent, currentIndent) {
if (value == null) return "null"
if (value is Bool) return value ? "true" : "false"
if (value is Num) {
if (value.isInfinity || value.isNan) return "null"
if (value == value.truncate) return value.truncate.toString
return value.toString
}
if (value is String) return escapeString_(value)
if (value is List) return stringifyList_(value, indent, currentIndent)
if (value is Map) return stringifyMap_(value, indent, currentIndent)
return "null"
}
static escapeString_(s) {
var result = "\""
for (c in s) {
if (c == "\"") {
result = result + "\\\""
} else if (c == "\\") {
result = result + "\\\\"
} else if (c == "\b") {
result = result + "\\b"
} else if (c == "\f") {
result = result + "\\f"
} else if (c == "\n") {
result = result + "\\n"
} else if (c == "\r") {
result = result + "\\r"
} else if (c == "\t") {
result = result + "\\t"
} else {
result = result + c
}
}
return result + "\""
}
static stringifyList_(list, indent, currentIndent) {
if (list.count == 0) return "[]"
var nextIndent = currentIndent + indent
var parts = []
for (item in list) {
parts.add(stringifyValue_(item, indent, nextIndent))
}
if (indent == "") {
return "[" + parts.join(",") + "]"
}
return "[\n" + nextIndent + parts.join(",\n" + nextIndent) + "\n" + currentIndent + "]"
}
static stringifyMap_(map, indent, currentIndent) {
if (map.count == 0) return "{}"
var nextIndent = currentIndent + indent
var parts = []
for (key in map.keys) {
var keyStr = escapeString_(key.toString)
var valueStr = stringifyValue_(map[key], indent, nextIndent)
if (indent == "") {
parts.add(keyStr + ":" + valueStr)
} else {
parts.add(keyStr + ": " + valueStr)
}
}
if (indent == "") {
return "{" + parts.join(",") + "}"
}
return "{\n" + nextIndent + parts.join(",\n" + nextIndent) + "\n" + currentIndent + "}"
}
}
+103
View File
@@ -0,0 +1,103 @@
// Please do not edit this file. It has been generated automatically
// from `src/module/json.wren` using `util/wren_to_c_string.py`
static const char* jsonModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"class Json {\n"
" foreign static parse(string)\n"
"\n"
" static stringify(value) { stringify_(value, \"\") }\n"
"\n"
" static stringify(value, indent) {\n"
" var indentStr = \"\"\n"
" if (indent is Num && indent > 0) {\n"
" for (i in 0...indent) {\n"
" indentStr = indentStr + \" \"\n"
" }\n"
" } else if (indent is String) {\n"
" indentStr = indent\n"
" }\n"
" return stringify_(value, indentStr)\n"
" }\n"
"\n"
" static stringify_(value, indent) {\n"
" return stringifyValue_(value, indent, \"\")\n"
" }\n"
"\n"
" static stringifyValue_(value, indent, currentIndent) {\n"
" if (value == null) return \"null\"\n"
" if (value is Bool) return value ? \"true\" : \"false\"\n"
" if (value is Num) {\n"
" if (value.isInfinity || value.isNan) return \"null\"\n"
" if (value == value.truncate) return value.truncate.toString\n"
" return value.toString\n"
" }\n"
" if (value is String) return escapeString_(value)\n"
" if (value is List) return stringifyList_(value, indent, currentIndent)\n"
" if (value is Map) return stringifyMap_(value, indent, currentIndent)\n"
" return \"null\"\n"
" }\n"
"\n"
" static escapeString_(s) {\n"
" var result = \"\\\"\"\n"
" for (c in s) {\n"
" if (c == \"\\\"\") {\n"
" result = result + \"\\\\\\\"\"\n"
" } else if (c == \"\\\\\") {\n"
" result = result + \"\\\\\\\\\"\n"
" } else if (c == \"\\b\") {\n"
" result = result + \"\\\\b\"\n"
" } else if (c == \"\\f\") {\n"
" result = result + \"\\\\f\"\n"
" } else if (c == \"\\n\") {\n"
" result = result + \"\\\\n\"\n"
" } else if (c == \"\\r\") {\n"
" result = result + \"\\\\r\"\n"
" } else if (c == \"\\t\") {\n"
" result = result + \"\\\\t\"\n"
" } else {\n"
" result = result + c\n"
" }\n"
" }\n"
" return result + \"\\\"\"\n"
" }\n"
"\n"
" static stringifyList_(list, indent, currentIndent) {\n"
" if (list.count == 0) return \"[]\"\n"
"\n"
" var nextIndent = currentIndent + indent\n"
" var parts = []\n"
" for (item in list) {\n"
" parts.add(stringifyValue_(item, indent, nextIndent))\n"
" }\n"
"\n"
" if (indent == \"\") {\n"
" return \"[\" + parts.join(\",\") + \"]\"\n"
" }\n"
"\n"
" return \"[\\n\" + nextIndent + parts.join(\",\\n\" + nextIndent) + \"\\n\" + currentIndent + \"]\"\n"
" }\n"
"\n"
" static stringifyMap_(map, indent, currentIndent) {\n"
" if (map.count == 0) return \"{}\"\n"
"\n"
" var nextIndent = currentIndent + indent\n"
" var parts = []\n"
" for (key in map.keys) {\n"
" var keyStr = escapeString_(key.toString)\n"
" var valueStr = stringifyValue_(map[key], indent, nextIndent)\n"
" if (indent == \"\") {\n"
" parts.add(keyStr + \":\" + valueStr)\n"
" } else {\n"
" parts.add(keyStr + \": \" + valueStr)\n"
" }\n"
" }\n"
"\n"
" if (indent == \"\") {\n"
" return \"{\" + parts.join(\",\") + \"}\"\n"
" }\n"
"\n"
" return \"{\\n\" + nextIndent + parts.join(\",\\n\" + nextIndent) + \"\\n\" + currentIndent + \"}\"\n"
" }\n"
"}\n";
+46
View File
@@ -0,0 +1,46 @@
// retoor <retoor@molodetz.nl>
class Math {
foreign static sin(x)
foreign static cos(x)
foreign static tan(x)
foreign static asin(x)
foreign static acos(x)
foreign static atan(x)
foreign static atan2(y, x)
foreign static sinh(x)
foreign static cosh(x)
foreign static tanh(x)
foreign static log(x)
foreign static log10(x)
foreign static log2(x)
foreign static exp(x)
foreign static pow(base, exp)
foreign static sqrt(x)
foreign static cbrt(x)
foreign static ceil(x)
foreign static floor(x)
foreign static round(x)
foreign static abs(x)
static min(a, b) { a < b ? a : b }
static max(a, b) { a > b ? a : b }
static clamp(x, min, max) { x < min ? min : (x > max ? max : x) }
static pi { 3.14159265358979323846 }
static e { 2.71828182845904523536 }
static tau { 6.28318530717958647692 }
static infinity { 1 / 0 }
static nan { 0 / 0 }
static degrees(radians) { radians * 180 / pi }
static radians(degrees) { degrees * pi / 180 }
static sign(x) {
if (x > 0) return 1
if (x < 0) return -1
return 0
}
static lerp(a, b, t) { a + (b - a) * t }
}
+50
View File
@@ -0,0 +1,50 @@
// Please do not edit this file. It has been generated automatically
// from `src/module/math.wren` using `util/wren_to_c_string.py`
static const char* mathModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"class Math {\n"
" foreign static sin(x)\n"
" foreign static cos(x)\n"
" foreign static tan(x)\n"
" foreign static asin(x)\n"
" foreign static acos(x)\n"
" foreign static atan(x)\n"
" foreign static atan2(y, x)\n"
" foreign static sinh(x)\n"
" foreign static cosh(x)\n"
" foreign static tanh(x)\n"
" foreign static log(x)\n"
" foreign static log10(x)\n"
" foreign static log2(x)\n"
" foreign static exp(x)\n"
" foreign static pow(base, exp)\n"
" foreign static sqrt(x)\n"
" foreign static cbrt(x)\n"
" foreign static ceil(x)\n"
" foreign static floor(x)\n"
" foreign static round(x)\n"
" foreign static abs(x)\n"
"\n"
" static min(a, b) { a < b ? a : b }\n"
" static max(a, b) { a > b ? a : b }\n"
" static clamp(x, min, max) { x < min ? min : (x > max ? max : x) }\n"
"\n"
" static pi { 3.14159265358979323846 }\n"
" static e { 2.71828182845904523536 }\n"
" static tau { 6.28318530717958647692 }\n"
" static infinity { 1 / 0 }\n"
" static nan { 0 / 0 }\n"
"\n"
" static degrees(radians) { radians * 180 / pi }\n"
" static radians(degrees) { degrees * pi / 180 }\n"
"\n"
" static sign(x) {\n"
" if (x > 0) return 1\n"
" if (x < 0) return -1\n"
" return 0\n"
" }\n"
"\n"
" static lerp(a, b, t) { a + (b - a) * t }\n"
"}\n";
+112
View File
@@ -0,0 +1,112 @@
// retoor <retoor@molodetz.nl>
#include <math.h>
#include "math_module.h"
#include "wren.h"
void mathSin(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, sin(x));
}
void mathCos(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, cos(x));
}
void mathTan(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, tan(x));
}
void mathAsin(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, asin(x));
}
void mathAcos(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, acos(x));
}
void mathAtan(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, atan(x));
}
void mathAtan2(WrenVM* vm) {
double y = wrenGetSlotDouble(vm, 1);
double x = wrenGetSlotDouble(vm, 2);
wrenSetSlotDouble(vm, 0, atan2(y, x));
}
void mathSinh(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, sinh(x));
}
void mathCosh(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, cosh(x));
}
void mathTanh(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, tanh(x));
}
void mathLog(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, log(x));
}
void mathLog10(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, log10(x));
}
void mathLog2(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, log2(x));
}
void mathExp(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, exp(x));
}
void mathPow(WrenVM* vm) {
double base = wrenGetSlotDouble(vm, 1);
double exponent = wrenGetSlotDouble(vm, 2);
wrenSetSlotDouble(vm, 0, pow(base, exponent));
}
void mathSqrt(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, sqrt(x));
}
void mathCbrt(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, cbrt(x));
}
void mathCeil(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, ceil(x));
}
void mathFloor(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, floor(x));
}
void mathRound(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, round(x));
}
void mathAbs(WrenVM* vm) {
double x = wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, fabs(x));
}
+30
View File
@@ -0,0 +1,30 @@
// retoor <retoor@molodetz.nl>
#ifndef wren_math_module_h
#define wren_math_module_h
#include "wren.h"
void mathSin(WrenVM* vm);
void mathCos(WrenVM* vm);
void mathTan(WrenVM* vm);
void mathAsin(WrenVM* vm);
void mathAcos(WrenVM* vm);
void mathAtan(WrenVM* vm);
void mathAtan2(WrenVM* vm);
void mathSinh(WrenVM* vm);
void mathCosh(WrenVM* vm);
void mathTanh(WrenVM* vm);
void mathLog(WrenVM* vm);
void mathLog10(WrenVM* vm);
void mathLog2(WrenVM* vm);
void mathExp(WrenVM* vm);
void mathPow(WrenVM* vm);
void mathSqrt(WrenVM* vm);
void mathCbrt(WrenVM* vm);
void mathCeil(WrenVM* vm);
void mathFloor(WrenVM* vm);
void mathRound(WrenVM* vm);
void mathAbs(WrenVM* vm);
#endif
+2 -3
View File
@@ -159,9 +159,8 @@ void socketWrite(WrenVM* vm)
SocketData* data = (SocketData*)wrenGetSlotForeign(vm, 0);
const char* text = wrenGetSlotString(vm, 1);
size_t length = strlen(text);
int length;
const char* text = wrenGetSlotBytes(vm, 1, &length);
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);
+447
View File
@@ -0,0 +1,447 @@
// retoor <retoor@molodetz.nl>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "regex.h"
#include "wren.h"
typedef struct {
char* pattern;
char* flags;
bool ignoreCase;
bool global;
bool multiline;
} RegexData;
typedef struct {
int type;
char literal;
const char* classStart;
const char* classEnd;
} PatternElement;
#define ELEM_LITERAL 0
#define ELEM_DOT 1
#define ELEM_CLASS 2
#define ELEM_ESCAPE 3
static bool matchElement(PatternElement* elem, char c, bool ignoreCase) {
if (c == '\0') return false;
if (elem->type == ELEM_DOT) {
return c != '\n';
}
if (elem->type == ELEM_LITERAL) {
char p = elem->literal;
if (ignoreCase) {
if (p >= 'A' && p <= 'Z') p += 32;
if (c >= 'A' && c <= 'Z') c += 32;
}
return p == c;
}
if (elem->type == ELEM_ESCAPE) {
switch (elem->literal) {
case 'd': return (c >= '0' && c <= '9');
case 'D': return !(c >= '0' && c <= '9');
case 'w': return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') || c == '_';
case 'W': return !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') || c == '_');
case 's': return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f';
case 'S': return !(c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f');
case 'n': return c == '\n';
case 't': return c == '\t';
case 'r': return c == '\r';
default: {
char p = elem->literal;
if (ignoreCase) {
if (p >= 'A' && p <= 'Z') p += 32;
if (c >= 'A' && c <= 'Z') c += 32;
}
return p == c;
}
}
}
if (elem->type == ELEM_CLASS) {
const char* p = elem->classStart;
bool negate = false;
bool matched = false;
if (*p == '^') {
negate = true;
p++;
}
while (p < elem->classEnd) {
if (p[1] == '-' && p + 2 < elem->classEnd) {
char lo = *p, hi = p[2];
char tc = c;
if (ignoreCase) {
if (lo >= 'A' && lo <= 'Z') lo += 32;
if (hi >= 'A' && hi <= 'Z') hi += 32;
if (tc >= 'A' && tc <= 'Z') tc += 32;
}
if (tc >= lo && tc <= hi) matched = true;
p += 3;
} else {
char pc = *p;
char tc = c;
if (ignoreCase) {
if (pc >= 'A' && pc <= 'Z') pc += 32;
if (tc >= 'A' && tc <= 'Z') tc += 32;
}
if (pc == tc) matched = true;
p++;
}
}
return negate ? !matched : matched;
}
return false;
}
static const char* parseElement(const char* pattern, PatternElement* elem) {
if (*pattern == '.') {
elem->type = ELEM_DOT;
return pattern + 1;
}
if (*pattern == '\\' && *(pattern + 1)) {
elem->type = ELEM_ESCAPE;
elem->literal = *(pattern + 1);
return pattern + 2;
}
if (*pattern == '[') {
elem->type = ELEM_CLASS;
elem->classStart = pattern + 1;
const char* p = pattern + 1;
if (*p == '^') p++;
if (*p == ']') p++;
while (*p && *p != ']') p++;
elem->classEnd = p;
return *p ? p + 1 : p;
}
elem->type = ELEM_LITERAL;
elem->literal = *pattern;
return pattern + 1;
}
static bool matchHere(const char* pattern, const char* text, bool ignoreCase, const char** matchEnd);
static bool matchStar(PatternElement* elem, const char* pattern, const char* text,
bool ignoreCase, const char** matchEnd) {
const char* t = text;
while (*t && matchElement(elem, *t, ignoreCase)) t++;
do {
if (matchHere(pattern, t, ignoreCase, matchEnd)) return true;
} while (t-- > text);
return false;
}
static bool matchPlus(PatternElement* elem, const char* pattern, const char* text,
bool ignoreCase, const char** matchEnd) {
if (!*text || !matchElement(elem, *text, ignoreCase)) return false;
return matchStar(elem, pattern, text + 1, ignoreCase, matchEnd);
}
static bool matchQuestion(PatternElement* elem, const char* pattern, const char* text,
bool ignoreCase, const char** matchEnd) {
if (matchHere(pattern, text, ignoreCase, matchEnd)) return true;
if (*text && matchElement(elem, *text, ignoreCase)) {
return matchHere(pattern, text + 1, ignoreCase, matchEnd);
}
return false;
}
static bool matchHere(const char* pattern, const char* text, bool ignoreCase, const char** matchEnd) {
if (*pattern == '\0') {
*matchEnd = text;
return true;
}
if (*pattern == '$' && *(pattern + 1) == '\0') {
*matchEnd = text;
return *text == '\0' || *text == '\n';
}
PatternElement elem;
const char* nextPattern = parseElement(pattern, &elem);
if (*nextPattern == '*') {
return matchStar(&elem, nextPattern + 1, text, ignoreCase, matchEnd);
}
if (*nextPattern == '+') {
return matchPlus(&elem, nextPattern + 1, text, ignoreCase, matchEnd);
}
if (*nextPattern == '?') {
return matchQuestion(&elem, nextPattern + 1, text, ignoreCase, matchEnd);
}
if (matchElement(&elem, *text, ignoreCase)) {
return matchHere(nextPattern, text + 1, ignoreCase, matchEnd);
}
return false;
}
static bool matchPattern(const char* pattern, const char* text, bool ignoreCase,
const char** matchStart, const char** matchEnd) {
if (*pattern == '^') {
*matchStart = text;
return matchHere(pattern + 1, text, ignoreCase, matchEnd);
}
do {
*matchStart = text;
if (matchHere(pattern, text, ignoreCase, matchEnd)) {
return true;
}
} while (*text++);
return false;
}
void regexAllocate(WrenVM* vm) {
RegexData* data = (RegexData*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(RegexData));
memset(data, 0, sizeof(RegexData));
const char* pattern = wrenGetSlotString(vm, 1);
data->pattern = strdup(pattern);
const char* flags = "";
if (wrenGetSlotCount(vm) > 2 && wrenGetSlotType(vm, 2) == WREN_TYPE_STRING) {
flags = wrenGetSlotString(vm, 2);
}
data->flags = strdup(flags);
for (const char* f = flags; *f; f++) {
switch (*f) {
case 'i': data->ignoreCase = true; break;
case 'g': data->global = true; break;
case 'm': data->multiline = true; break;
}
}
}
void regexFinalize(void* ptr) {
RegexData* data = (RegexData*)ptr;
if (data->pattern) free(data->pattern);
if (data->flags) free(data->flags);
}
void regexTest(WrenVM* vm) {
RegexData* data = (RegexData*)wrenGetSlotForeign(vm, 0);
const char* text = wrenGetSlotString(vm, 1);
const char* matchStart;
const char* matchEnd;
bool result = matchPattern(data->pattern, text, data->ignoreCase, &matchStart, &matchEnd);
wrenSetSlotBool(vm, 0, result);
}
void regexMatch(WrenVM* vm) {
RegexData* data = (RegexData*)wrenGetSlotForeign(vm, 0);
const char* text = wrenGetSlotString(vm, 1);
const char* matchStart;
const char* matchEnd;
if (!matchPattern(data->pattern, text, data->ignoreCase, &matchStart, &matchEnd)) {
wrenSetSlotNull(vm, 0);
return;
}
wrenEnsureSlots(vm, 7);
wrenGetVariable(vm, "regex", "Match", 1);
size_t matchLen = matchEnd - matchStart;
char* matchText = (char*)malloc(matchLen + 1);
memcpy(matchText, matchStart, matchLen);
matchText[matchLen] = '\0';
wrenSetSlotString(vm, 2, matchText);
wrenSetSlotDouble(vm, 3, (double)(matchStart - text));
wrenSetSlotDouble(vm, 4, (double)(matchEnd - text));
wrenSetSlotNewList(vm, 5);
wrenSetSlotString(vm, 6, matchText);
wrenInsertInList(vm, 5, -1, 6);
free(matchText);
WrenHandle* newMethod = wrenMakeCallHandle(vm, "new_(_,_,_,_)");
wrenCall(vm, newMethod);
wrenReleaseHandle(vm, newMethod);
}
void regexMatchAll(WrenVM* vm) {
RegexData* data = (RegexData*)wrenGetSlotForeign(vm, 0);
const char* text = wrenGetSlotString(vm, 1);
const char* cursor = text;
const char* matchStart;
const char* matchEnd;
wrenEnsureSlots(vm, 8);
wrenSetSlotNewList(vm, 0);
while (matchPattern(data->pattern, cursor, data->ignoreCase, &matchStart, &matchEnd)) {
wrenGetVariable(vm, "regex", "Match", 1);
size_t matchLen = matchEnd - matchStart;
char* matchText = (char*)malloc(matchLen + 1);
memcpy(matchText, matchStart, matchLen);
matchText[matchLen] = '\0';
wrenSetSlotString(vm, 2, matchText);
wrenSetSlotDouble(vm, 3, (double)(matchStart - text));
wrenSetSlotDouble(vm, 4, (double)(matchEnd - text));
wrenSetSlotNewList(vm, 5);
wrenSetSlotString(vm, 6, matchText);
wrenInsertInList(vm, 5, -1, 6);
free(matchText);
WrenHandle* newMethod = wrenMakeCallHandle(vm, "new_(_,_,_,_)");
wrenCall(vm, newMethod);
wrenReleaseHandle(vm, newMethod);
wrenInsertInList(vm, 0, -1, 1);
if (matchEnd == cursor) cursor++;
else cursor = matchEnd;
if (*cursor == '\0') break;
if (!data->global) break;
}
}
void regexReplace(WrenVM* vm) {
RegexData* data = (RegexData*)wrenGetSlotForeign(vm, 0);
const char* text = wrenGetSlotString(vm, 1);
const char* replacement = wrenGetSlotString(vm, 2);
const char* matchStart;
const char* matchEnd;
if (!matchPattern(data->pattern, text, data->ignoreCase, &matchStart, &matchEnd)) {
wrenSetSlotString(vm, 0, text);
return;
}
size_t textLen = strlen(text);
size_t replLen = strlen(replacement);
size_t matchLen = matchEnd - matchStart;
size_t resultLen = textLen - matchLen + replLen;
char* result = (char*)malloc(resultLen + 1);
size_t prefixLen = matchStart - text;
memcpy(result, text, prefixLen);
memcpy(result + prefixLen, replacement, replLen);
memcpy(result + prefixLen + replLen, matchEnd, textLen - prefixLen - matchLen);
result[resultLen] = '\0';
wrenSetSlotString(vm, 0, result);
free(result);
}
void regexReplaceAll(WrenVM* vm) {
RegexData* data = (RegexData*)wrenGetSlotForeign(vm, 0);
const char* text = wrenGetSlotString(vm, 1);
const char* replacement = wrenGetSlotString(vm, 2);
size_t textLen = strlen(text);
size_t replLen = strlen(replacement);
size_t capacity = textLen * 2 + 1;
char* result = (char*)malloc(capacity);
size_t resultLen = 0;
const char* cursor = text;
const char* matchStart;
const char* matchEnd;
while (matchPattern(data->pattern, cursor, data->ignoreCase, &matchStart, &matchEnd)) {
size_t prefixLen = matchStart - cursor;
while (resultLen + prefixLen + replLen >= capacity) {
capacity *= 2;
result = (char*)realloc(result, capacity);
}
memcpy(result + resultLen, cursor, prefixLen);
resultLen += prefixLen;
memcpy(result + resultLen, replacement, replLen);
resultLen += replLen;
if (matchEnd == cursor) {
if (*cursor) {
result[resultLen++] = *cursor;
}
cursor++;
} else {
cursor = matchEnd;
}
if (*cursor == '\0') break;
}
size_t remaining = strlen(cursor);
while (resultLen + remaining >= capacity) {
capacity *= 2;
result = (char*)realloc(result, capacity);
}
memcpy(result + resultLen, cursor, remaining);
resultLen += remaining;
result[resultLen] = '\0';
wrenSetSlotString(vm, 0, result);
free(result);
}
void regexSplit(WrenVM* vm) {
RegexData* data = (RegexData*)wrenGetSlotForeign(vm, 0);
const char* text = wrenGetSlotString(vm, 1);
wrenEnsureSlots(vm, 3);
wrenSetSlotNewList(vm, 0);
const char* cursor = text;
const char* matchStart;
const char* matchEnd;
while (matchPattern(data->pattern, cursor, data->ignoreCase, &matchStart, &matchEnd)) {
size_t partLen = matchStart - cursor;
char* part = (char*)malloc(partLen + 1);
memcpy(part, cursor, partLen);
part[partLen] = '\0';
wrenSetSlotString(vm, 2, part);
wrenInsertInList(vm, 0, -1, 2);
free(part);
if (matchEnd == cursor) cursor++;
else cursor = matchEnd;
if (*cursor == '\0') break;
}
wrenSetSlotString(vm, 2, cursor);
wrenInsertInList(vm, 0, -1, 2);
}
void regexPattern(WrenVM* vm) {
RegexData* data = (RegexData*)wrenGetSlotForeign(vm, 0);
wrenSetSlotString(vm, 0, data->pattern);
}
void regexFlags(WrenVM* vm) {
RegexData* data = (RegexData*)wrenGetSlotForeign(vm, 0);
wrenSetSlotString(vm, 0, data->flags);
}
+19
View File
@@ -0,0 +1,19 @@
// retoor <retoor@molodetz.nl>
#ifndef wren_regex_h
#define wren_regex_h
#include "wren.h"
void regexAllocate(WrenVM* vm);
void regexFinalize(void* data);
void regexTest(WrenVM* vm);
void regexMatch(WrenVM* vm);
void regexMatchAll(WrenVM* vm);
void regexReplace(WrenVM* vm);
void regexReplaceAll(WrenVM* vm);
void regexSplit(WrenVM* vm);
void regexPattern(WrenVM* vm);
void regexFlags(WrenVM* vm);
#endif
+36
View File
@@ -0,0 +1,36 @@
// retoor <retoor@molodetz.nl>
foreign class Regex {
construct new(pattern) {}
construct new(pattern, flags) {}
foreign test(string)
foreign match(string)
foreign matchAll(string)
foreign replace(string, replacement)
foreign replaceAll(string, replacement)
foreign split(string)
foreign pattern
foreign flags
}
class Match {
construct new_(text, start, end, groups) {
_text = text
_start = start
_end = end
_groups = groups
}
text { _text }
start { _start }
end { _end }
groups { _groups }
group(index) {
if (index < 0 || index >= _groups.count) return null
return _groups[index]
}
toString { _text }
}
+40
View File
@@ -0,0 +1,40 @@
// Please do not edit this file. It has been generated automatically
// from `src/module/regex.wren` using `util/wren_to_c_string.py`
static const char* regexModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"foreign class Regex {\n"
" construct new(pattern) {}\n"
" construct new(pattern, flags) {}\n"
"\n"
" foreign test(string)\n"
" foreign match(string)\n"
" foreign matchAll(string)\n"
" foreign replace(string, replacement)\n"
" foreign replaceAll(string, replacement)\n"
" foreign split(string)\n"
" foreign pattern\n"
" foreign flags\n"
"}\n"
"\n"
"class Match {\n"
" construct new_(text, start, end, groups) {\n"
" _text = text\n"
" _start = start\n"
" _end = end\n"
" _groups = groups\n"
" }\n"
"\n"
" text { _text }\n"
" start { _start }\n"
" end { _end }\n"
" groups { _groups }\n"
"\n"
" group(index) {\n"
" if (index < 0 || index >= _groups.count) return null\n"
" return _groups[index]\n"
" }\n"
"\n"
" toString { _text }\n"
"}\n";
+35
View File
@@ -0,0 +1,35 @@
// retoor <retoor@molodetz.nl>
class Signal {
static SIGHUP { 1 }
static SIGINT { 2 }
static SIGQUIT { 3 }
static SIGTERM { 15 }
static SIGUSR1 { 10 }
static SIGUSR2 { 12 }
foreign static trap_(signum, fiber)
foreign static ignore_(signum)
foreign static reset_(signum)
static trap(signum, fn) {
if (!(signum is Num)) Fiber.abort("Signal number must be a number.")
var fiber = Fiber.new {
while (true) {
fn.call()
Fiber.yield()
}
}
trap_(signum, fiber)
}
static ignore(signum) {
if (!(signum is Num)) Fiber.abort("Signal number must be a number.")
ignore_(signum)
}
static reset(signum) {
if (!(signum is Num)) Fiber.abort("Signal number must be a number.")
reset_(signum)
}
}
+39
View File
@@ -0,0 +1,39 @@
// Please do not edit this file. It has been generated automatically
// from `src/module/signal.wren` using `util/wren_to_c_string.py`
static const char* signalModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"class Signal {\n"
" static SIGHUP { 1 }\n"
" static SIGINT { 2 }\n"
" static SIGQUIT { 3 }\n"
" static SIGTERM { 15 }\n"
" static SIGUSR1 { 10 }\n"
" static SIGUSR2 { 12 }\n"
"\n"
" foreign static trap_(signum, fiber)\n"
" foreign static ignore_(signum)\n"
" foreign static reset_(signum)\n"
"\n"
" static trap(signum, fn) {\n"
" if (!(signum is Num)) Fiber.abort(\"Signal number must be a number.\")\n"
" var fiber = Fiber.new {\n"
" while (true) {\n"
" fn.call()\n"
" Fiber.yield()\n"
" }\n"
" }\n"
" trap_(signum, fiber)\n"
" }\n"
"\n"
" static ignore(signum) {\n"
" if (!(signum is Num)) Fiber.abort(\"Signal number must be a number.\")\n"
" ignore_(signum)\n"
" }\n"
"\n"
" static reset(signum) {\n"
" if (!(signum is Num)) Fiber.abort(\"Signal number must be a number.\")\n"
" reset_(signum)\n"
" }\n"
"}\n";
+128
View File
@@ -0,0 +1,128 @@
// retoor <retoor@molodetz.nl>
#include <stdlib.h>
#include <string.h>
#include "signal_module.h"
#include "scheduler.h"
#include "wren.h"
#include "vm.h"
#include "uv.h"
#define MAX_SIGNALS 32
static uv_signal_t* signalHandles[MAX_SIGNALS] = {NULL};
static WrenHandle* signalFibers[MAX_SIGNALS] = {NULL};
static void signalCallback(uv_signal_t* handle, int signum) {
if (signum < 0 || signum >= MAX_SIGNALS) return;
WrenHandle* fiber = signalFibers[signum];
if (fiber == NULL) return;
schedulerResume(fiber, false);
}
void signalTrap(WrenVM* vm) {
int signum = (int)wrenGetSlotDouble(vm, 1);
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);
if (signum < 0 || signum >= MAX_SIGNALS) {
wrenSetSlotString(vm, 0, "Invalid signal number.");
wrenAbortFiber(vm, 0);
return;
}
if (signalHandles[signum] != NULL) {
uv_signal_stop(signalHandles[signum]);
uv_close((uv_handle_t*)signalHandles[signum], NULL);
free(signalHandles[signum]);
}
if (signalFibers[signum] != NULL) {
wrenReleaseHandle(vm, signalFibers[signum]);
}
uv_signal_t* handle = (uv_signal_t*)malloc(sizeof(uv_signal_t));
if (handle == NULL) {
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
uv_signal_init(getLoop(), handle);
signalHandles[signum] = handle;
signalFibers[signum] = fiber;
int result = uv_signal_start(handle, signalCallback, signum);
if (result != 0) {
free(handle);
signalHandles[signum] = NULL;
signalFibers[signum] = NULL;
wrenReleaseHandle(vm, fiber);
wrenSetSlotString(vm, 0, uv_strerror(result));
wrenAbortFiber(vm, 0);
return;
}
}
void signalIgnore(WrenVM* vm) {
int signum = (int)wrenGetSlotDouble(vm, 1);
if (signum < 0 || signum >= MAX_SIGNALS) {
wrenSetSlotString(vm, 0, "Invalid signal number.");
wrenAbortFiber(vm, 0);
return;
}
if (signalHandles[signum] != NULL) {
uv_signal_stop(signalHandles[signum]);
uv_close((uv_handle_t*)signalHandles[signum], NULL);
free(signalHandles[signum]);
signalHandles[signum] = NULL;
}
if (signalFibers[signum] != NULL) {
wrenReleaseHandle(vm, signalFibers[signum]);
signalFibers[signum] = NULL;
}
uv_signal_t* handle = (uv_signal_t*)malloc(sizeof(uv_signal_t));
if (handle == NULL) {
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
uv_signal_init(getLoop(), handle);
signalHandles[signum] = handle;
int result = uv_signal_start(handle, NULL, signum);
if (result != 0) {
free(handle);
signalHandles[signum] = NULL;
wrenSetSlotString(vm, 0, uv_strerror(result));
wrenAbortFiber(vm, 0);
}
}
void signalReset(WrenVM* vm) {
int signum = (int)wrenGetSlotDouble(vm, 1);
if (signum < 0 || signum >= MAX_SIGNALS) {
wrenSetSlotString(vm, 0, "Invalid signal number.");
wrenAbortFiber(vm, 0);
return;
}
if (signalHandles[signum] != NULL) {
uv_signal_stop(signalHandles[signum]);
uv_close((uv_handle_t*)signalHandles[signum], NULL);
free(signalHandles[signum]);
signalHandles[signum] = NULL;
}
if (signalFibers[signum] != NULL) {
wrenReleaseHandle(vm, signalFibers[signum]);
signalFibers[signum] = NULL;
}
}
+12
View File
@@ -0,0 +1,12 @@
// retoor <retoor@molodetz.nl>
#ifndef wren_signal_module_h
#define wren_signal_module_h
#include "wren.h"
void signalTrap(WrenVM* vm);
void signalIgnore(WrenVM* vm);
void signalReset(WrenVM* vm);
#endif
+14
View File
@@ -0,0 +1,14 @@
// retoor <retoor@molodetz.nl>
foreign class Database {
construct open(path) {}
construct memory() {}
foreign execute(sql)
foreign execute(sql, params)
foreign query(sql)
foreign query(sql, params)
foreign close()
foreign lastInsertId
foreign changes
}
+18
View File
@@ -0,0 +1,18 @@
// Please do not edit this file. It has been generated automatically
// from `src/module/sqlite.wren` using `util/wren_to_c_string.py`
static const char* sqliteModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"foreign class Database {\n"
" construct open(path) {}\n"
" construct memory() {}\n"
"\n"
" foreign execute(sql)\n"
" foreign execute(sql, params)\n"
" foreign query(sql)\n"
" foreign query(sql, params)\n"
" foreign close()\n"
" foreign lastInsertId\n"
" foreign changes\n"
"}\n";
+219
View File
@@ -0,0 +1,219 @@
// retoor <retoor@molodetz.nl>
#include <stdlib.h>
#include <string.h>
#include "sqlite_module.h"
#include "wren.h"
#include "sqlite3.h"
typedef struct {
sqlite3* db;
} SqliteData;
void sqliteAllocate(WrenVM* vm) {
SqliteData* data = (SqliteData*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(SqliteData));
data->db = NULL;
const char* path = ":memory:";
if (wrenGetSlotCount(vm) > 1 && wrenGetSlotType(vm, 1) == WREN_TYPE_STRING) {
path = wrenGetSlotString(vm, 1);
}
int result = sqlite3_open(path, &data->db);
if (result != SQLITE_OK) {
const char* err = sqlite3_errmsg(data->db);
sqlite3_close(data->db);
data->db = NULL;
wrenSetSlotString(vm, 0, err);
wrenAbortFiber(vm, 0);
}
}
void sqliteFinalize(void* ptr) {
SqliteData* data = (SqliteData*)ptr;
if (data->db != NULL) {
sqlite3_close(data->db);
data->db = NULL;
}
}
static void bindParams(WrenVM* vm, sqlite3_stmt* stmt, int paramsSlot) {
int count = wrenGetListCount(vm, paramsSlot);
wrenEnsureSlots(vm, paramsSlot + 2);
for (int i = 0; i < count; i++) {
wrenGetListElement(vm, paramsSlot, i, paramsSlot + 1);
WrenType type = wrenGetSlotType(vm, paramsSlot + 1);
switch (type) {
case WREN_TYPE_NULL:
sqlite3_bind_null(stmt, i + 1);
break;
case WREN_TYPE_BOOL:
sqlite3_bind_int(stmt, i + 1, wrenGetSlotBool(vm, paramsSlot + 1) ? 1 : 0);
break;
case WREN_TYPE_NUM:
sqlite3_bind_double(stmt, i + 1, wrenGetSlotDouble(vm, paramsSlot + 1));
break;
case WREN_TYPE_STRING:
sqlite3_bind_text(stmt, i + 1, wrenGetSlotString(vm, paramsSlot + 1), -1, SQLITE_TRANSIENT);
break;
default:
sqlite3_bind_null(stmt, i + 1);
break;
}
}
}
static void executeInternal(WrenVM* vm, bool hasParams) {
SqliteData* data = (SqliteData*)wrenGetSlotForeign(vm, 0);
if (data->db == NULL) {
wrenSetSlotString(vm, 0, "Database is closed.");
wrenAbortFiber(vm, 0);
return;
}
const char* sql = wrenGetSlotString(vm, 1);
sqlite3_stmt* stmt;
int result = sqlite3_prepare_v2(data->db, sql, -1, &stmt, NULL);
if (result != SQLITE_OK) {
wrenSetSlotString(vm, 0, sqlite3_errmsg(data->db));
wrenAbortFiber(vm, 0);
return;
}
if (hasParams) {
bindParams(vm, stmt, 2);
}
result = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (result != SQLITE_DONE && result != SQLITE_ROW) {
wrenSetSlotString(vm, 0, sqlite3_errmsg(data->db));
wrenAbortFiber(vm, 0);
return;
}
wrenSetSlotNull(vm, 0);
}
void sqliteExecute(WrenVM* vm) {
executeInternal(vm, false);
}
void sqliteExecuteParams(WrenVM* vm) {
executeInternal(vm, true);
}
static void queryInternal(WrenVM* vm, bool hasParams) {
SqliteData* data = (SqliteData*)wrenGetSlotForeign(vm, 0);
if (data->db == NULL) {
wrenSetSlotString(vm, 0, "Database is closed.");
wrenAbortFiber(vm, 0);
return;
}
const char* sql = wrenGetSlotString(vm, 1);
sqlite3_stmt* stmt;
int result = sqlite3_prepare_v2(data->db, sql, -1, &stmt, NULL);
if (result != SQLITE_OK) {
wrenSetSlotString(vm, 0, sqlite3_errmsg(data->db));
wrenAbortFiber(vm, 0);
return;
}
if (hasParams) {
bindParams(vm, stmt, 2);
}
wrenEnsureSlots(vm, 5);
wrenSetSlotNewList(vm, 0);
int colCount = sqlite3_column_count(stmt);
while ((result = sqlite3_step(stmt)) == SQLITE_ROW) {
wrenSetSlotNewMap(vm, 3);
for (int i = 0; i < colCount; i++) {
const char* colName = sqlite3_column_name(stmt, i);
wrenSetSlotString(vm, 1, colName);
int colType = sqlite3_column_type(stmt, i);
switch (colType) {
case SQLITE_NULL:
wrenSetSlotNull(vm, 4);
break;
case SQLITE_INTEGER:
wrenSetSlotDouble(vm, 4, (double)sqlite3_column_int64(stmt, i));
break;
case SQLITE_FLOAT:
wrenSetSlotDouble(vm, 4, sqlite3_column_double(stmt, i));
break;
case SQLITE_TEXT:
wrenSetSlotString(vm, 4, (const char*)sqlite3_column_text(stmt, i));
break;
case SQLITE_BLOB: {
int size = sqlite3_column_bytes(stmt, i);
const unsigned char* blob = sqlite3_column_blob(stmt, i);
wrenSetSlotNewList(vm, 4);
for (int j = 0; j < size; j++) {
wrenSetSlotDouble(vm, 2, (double)blob[j]);
wrenInsertInList(vm, 4, -1, 2);
}
break;
}
default:
wrenSetSlotNull(vm, 4);
break;
}
wrenSetMapValue(vm, 3, 1, 4);
}
wrenInsertInList(vm, 0, -1, 3);
}
sqlite3_finalize(stmt);
if (result != SQLITE_DONE) {
wrenSetSlotString(vm, 0, sqlite3_errmsg(data->db));
wrenAbortFiber(vm, 0);
}
}
void sqliteQuery(WrenVM* vm) {
queryInternal(vm, false);
}
void sqliteQueryParams(WrenVM* vm) {
queryInternal(vm, true);
}
void sqliteClose(WrenVM* vm) {
SqliteData* data = (SqliteData*)wrenGetSlotForeign(vm, 0);
if (data->db != NULL) {
sqlite3_close(data->db);
data->db = NULL;
}
}
void sqliteLastInsertId(WrenVM* vm) {
SqliteData* data = (SqliteData*)wrenGetSlotForeign(vm, 0);
if (data->db == NULL) {
wrenSetSlotDouble(vm, 0, 0);
return;
}
wrenSetSlotDouble(vm, 0, (double)sqlite3_last_insert_rowid(data->db));
}
void sqliteChanges(WrenVM* vm) {
SqliteData* data = (SqliteData*)wrenGetSlotForeign(vm, 0);
if (data->db == NULL) {
wrenSetSlotDouble(vm, 0, 0);
return;
}
wrenSetSlotDouble(vm, 0, (double)sqlite3_changes(data->db));
}
+18
View File
@@ -0,0 +1,18 @@
// retoor <retoor@molodetz.nl>
#ifndef wren_sqlite_h
#define wren_sqlite_h
#include "wren.h"
void sqliteAllocate(WrenVM* vm);
void sqliteFinalize(void* data);
void sqliteExecute(WrenVM* vm);
void sqliteExecuteParams(WrenVM* vm);
void sqliteQuery(WrenVM* vm);
void sqliteQueryParams(WrenVM* vm);
void sqliteClose(WrenVM* vm);
void sqliteLastInsertId(WrenVM* vm);
void sqliteChanges(WrenVM* vm);
#endif
+184
View File
@@ -0,0 +1,184 @@
// retoor <retoor@molodetz.nl>
#include <stdlib.h>
#include <string.h>
#include "subprocess.h"
#include "scheduler.h"
#include "wren.h"
#include "vm.h"
#include "uv.h"
typedef struct {
WrenHandle* fiber;
uv_process_t process;
uv_pipe_t stdoutPipe;
uv_pipe_t stderrPipe;
char* stdoutBuf;
size_t stdoutLen;
size_t stdoutCap;
char* stderrBuf;
size_t stderrLen;
size_t stderrCap;
int64_t exitCode;
int handlesOpen;
} ProcessData;
static void appendBuffer(char** buf, size_t* len, size_t* cap, const char* data, size_t dataLen) {
if (*len + dataLen >= *cap) {
size_t newCap = (*cap == 0) ? 1024 : *cap * 2;
while (newCap < *len + dataLen + 1) newCap *= 2;
*buf = (char*)realloc(*buf, newCap);
*cap = newCap;
}
memcpy(*buf + *len, data, dataLen);
*len += dataLen;
(*buf)[*len] = '\0';
}
static void onHandleClosed(uv_handle_t* handle) {
ProcessData* data = (ProcessData*)handle->data;
data->handlesOpen--;
if (data->handlesOpen > 0) return;
WrenVM* vm = getVM();
WrenHandle* fiber = data->fiber;
wrenEnsureSlots(vm, 4);
wrenSetSlotNewList(vm, 2);
wrenSetSlotDouble(vm, 3, (double)data->exitCode);
wrenInsertInList(vm, 2, -1, 3);
wrenSetSlotString(vm, 3, data->stdoutBuf != NULL ? data->stdoutBuf : "");
wrenInsertInList(vm, 2, -1, 3);
wrenSetSlotString(vm, 3, data->stderrBuf != NULL ? data->stderrBuf : "");
wrenInsertInList(vm, 2, -1, 3);
if (data->stdoutBuf != NULL) free(data->stdoutBuf);
if (data->stderrBuf != NULL) free(data->stderrBuf);
free(data);
schedulerResume(fiber, true);
schedulerFinishResume();
}
static void allocBuffer(uv_handle_t* handle, size_t size, uv_buf_t* buf) {
buf->base = (char*)malloc(size);
buf->len = size;
}
static void onStdoutRead(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
ProcessData* data = (ProcessData*)stream->data;
if (nread > 0) {
appendBuffer(&data->stdoutBuf, &data->stdoutLen, &data->stdoutCap, buf->base, nread);
}
if (buf->base != NULL) free(buf->base);
if (nread < 0) {
uv_close((uv_handle_t*)stream, onHandleClosed);
}
}
static void onStderrRead(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
ProcessData* data = (ProcessData*)stream->data;
if (nread > 0) {
appendBuffer(&data->stderrBuf, &data->stderrLen, &data->stderrCap, buf->base, nread);
}
if (buf->base != NULL) free(buf->base);
if (nread < 0) {
uv_close((uv_handle_t*)stream, onHandleClosed);
}
}
static void onProcessExit(uv_process_t* process, int64_t exitStatus, int termSignal) {
ProcessData* data = (ProcessData*)process->data;
data->exitCode = exitStatus;
uv_close((uv_handle_t*)process, onHandleClosed);
}
void subprocessRun(WrenVM* vm) {
int argCount = wrenGetListCount(vm, 1);
if (argCount == 0) {
wrenSetSlotString(vm, 0, "Command list cannot be empty.");
wrenAbortFiber(vm, 0);
return;
}
char** args = (char**)malloc((argCount + 1) * sizeof(char*));
if (args == NULL) {
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
wrenEnsureSlots(vm, 4);
for (int i = 0; i < argCount; i++) {
wrenGetListElement(vm, 1, i, 3);
const char* arg = wrenGetSlotString(vm, 3);
args[i] = strdup(arg);
}
args[argCount] = NULL;
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);
ProcessData* data = (ProcessData*)calloc(1, sizeof(ProcessData));
if (data == NULL) {
for (int i = 0; i < argCount; i++) free(args[i]);
free(args);
wrenReleaseHandle(vm, fiber);
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
data->fiber = fiber;
data->handlesOpen = 3;
uv_loop_t* loop = getLoop();
uv_pipe_init(loop, &data->stdoutPipe, 0);
uv_pipe_init(loop, &data->stderrPipe, 0);
data->stdoutPipe.data = data;
data->stderrPipe.data = data;
data->process.data = data;
uv_stdio_container_t stdio[3];
stdio[0].flags = UV_IGNORE;
stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
stdio[1].data.stream = (uv_stream_t*)&data->stdoutPipe;
stdio[2].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
stdio[2].data.stream = (uv_stream_t*)&data->stderrPipe;
uv_process_options_t options;
memset(&options, 0, sizeof(options));
options.file = args[0];
options.args = args;
options.stdio = stdio;
options.stdio_count = 3;
options.exit_cb = onProcessExit;
int result = uv_spawn(loop, &data->process, &options);
for (int i = 0; i < argCount; i++) free(args[i]);
free(args);
if (result != 0) {
data->handlesOpen = 2;
uv_close((uv_handle_t*)&data->stdoutPipe, onHandleClosed);
uv_close((uv_handle_t*)&data->stderrPipe, onHandleClosed);
wrenReleaseHandle(vm, fiber);
wrenSetSlotString(vm, 0, uv_strerror(result));
wrenAbortFiber(vm, 0);
return;
}
uv_read_start((uv_stream_t*)&data->stdoutPipe, allocBuffer, onStdoutRead);
uv_read_start((uv_stream_t*)&data->stderrPipe, allocBuffer, onStderrRead);
}
+10
View File
@@ -0,0 +1,10 @@
// retoor <retoor@molodetz.nl>
#ifndef wren_subprocess_h
#define wren_subprocess_h
#include "wren.h"
void subprocessRun(WrenVM* vm);
#endif
+47
View File
@@ -0,0 +1,47 @@
// retoor <retoor@molodetz.nl>
import "scheduler" for Scheduler
class ProcessResult {
construct new_(exitCode, stdout, stderr) {
_exitCode = exitCode
_stdout = stdout
_stderr = stderr
}
exitCode { _exitCode }
stdout { _stdout }
stderr { _stderr }
success { _exitCode == 0 }
toString {
if (success) return _stdout
return "Process exited with code %(_exitCode): %(_stderr)"
}
}
class Subprocess {
foreign static run_(args, fiber)
static run(command) {
if (command is String) {
return run(["/bin/sh", "-c", command])
}
if (!(command is List)) Fiber.abort("Command must be a String or List.")
if (command.count == 0) Fiber.abort("Command list cannot be empty.")
run_(command, Fiber.current)
var result = Scheduler.runNextScheduled_()
return ProcessResult.new_(result[0], result[1], result[2])
}
static run(command, cwd) {
if (command is String) {
return run(["/bin/sh", "-c", command], cwd)
}
if (!(command is List)) Fiber.abort("Command must be a String or List.")
if (command.count == 0) Fiber.abort("Command list cannot be empty.")
run_(command, Fiber.current)
var result = Scheduler.runNextScheduled_()
return ProcessResult.new_(result[0], result[1], result[2])
}
}
+51
View File
@@ -0,0 +1,51 @@
// Please do not edit this file. It has been generated automatically
// from `src/module/subprocess.wren` using `util/wren_to_c_string.py`
static const char* subprocessModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"import \"scheduler\" for Scheduler\n"
"\n"
"class ProcessResult {\n"
" construct new_(exitCode, stdout, stderr) {\n"
" _exitCode = exitCode\n"
" _stdout = stdout\n"
" _stderr = stderr\n"
" }\n"
"\n"
" exitCode { _exitCode }\n"
" stdout { _stdout }\n"
" stderr { _stderr }\n"
" success { _exitCode == 0 }\n"
"\n"
" toString {\n"
" if (success) return _stdout\n"
" return \"Process exited with code %(_exitCode): %(_stderr)\"\n"
" }\n"
"}\n"
"\n"
"class Subprocess {\n"
" foreign static run_(args, fiber)\n"
"\n"
" static run(command) {\n"
" if (command is String) {\n"
" return run([\"/bin/sh\", \"-c\", command])\n"
" }\n"
" if (!(command is List)) Fiber.abort(\"Command must be a String or List.\")\n"
" if (command.count == 0) Fiber.abort(\"Command list cannot be empty.\")\n"
" run_(command, Fiber.current)\n"
" var result = Scheduler.runNextScheduled_()\n"
" return ProcessResult.new_(result[0], result[1], result[2])\n"
" }\n"
"\n"
" static run(command, cwd) {\n"
" if (command is String) {\n"
" return run([\"/bin/sh\", \"-c\", command], cwd)\n"
" }\n"
" if (!(command is List)) Fiber.abort(\"Command must be a String or List.\")\n"
" if (command.count == 0) Fiber.abort(\"Command list cannot be empty.\")\n"
" run_(command, Fiber.current)\n"
" var result = Scheduler.runNextScheduled_()\n"
" return ProcessResult.new_(result[0], result[1], result[2])\n"
" }\n"
"}\n";
+403
View File
@@ -0,0 +1,403 @@
// retoor <retoor@molodetz.nl>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/x509v3.h>
#include "uv.h"
#include "vm.h"
#include "scheduler.h"
#include "tls.h"
static SSL_CTX* globalCtx = NULL;
static bool sslInitialized = false;
typedef enum {
TLS_STATE_DISCONNECTED,
TLS_STATE_CONNECTING,
TLS_STATE_HANDSHAKING,
TLS_STATE_CONNECTED,
TLS_STATE_ERROR
} TlsState;
typedef struct {
uv_tcp_t* handle;
SSL* ssl;
BIO* readBio;
BIO* writeBio;
WrenHandle* fiber;
TlsState state;
char* hostname;
char* readBuffer;
size_t readBufferSize;
bool wantRead;
bool writeInProgress;
} TlsSocketData;
static void initOpenSSL() {
if (sslInitialized) return;
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
globalCtx = SSL_CTX_new(TLS_client_method());
if (globalCtx) {
SSL_CTX_set_default_verify_paths(globalCtx);
SSL_CTX_set_verify(globalCtx, SSL_VERIFY_PEER, NULL);
SSL_CTX_set_mode(globalCtx, SSL_MODE_AUTO_RETRY);
}
sslInitialized = true;
}
static void tlsCloseCallback(uv_handle_t* handle) {
free(handle);
}
static void flushWriteBio(TlsSocketData* data);
static void doHandshake(TlsSocketData* data);
void tlsSocketAllocate(WrenVM* vm) {
initOpenSSL();
TlsSocketData* data = (TlsSocketData*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(TlsSocketData));
memset(data, 0, sizeof(TlsSocketData));
data->handle = (uv_tcp_t*)malloc(sizeof(uv_tcp_t));
uv_tcp_init(getLoop(), data->handle);
data->handle->data = data;
data->ssl = SSL_new(globalCtx);
data->readBio = BIO_new(BIO_s_mem());
data->writeBio = BIO_new(BIO_s_mem());
SSL_set_bio(data->ssl, data->readBio, data->writeBio);
SSL_set_connect_state(data->ssl);
data->state = TLS_STATE_DISCONNECTED;
data->readBufferSize = 16384;
data->readBuffer = (char*)malloc(data->readBufferSize);
}
void tlsSocketFinalize(void* ptr) {
TlsSocketData* data = (TlsSocketData*)ptr;
if (data->ssl) {
SSL_free(data->ssl);
data->ssl = NULL;
}
if (data->handle) {
uv_close((uv_handle_t*)data->handle, tlsCloseCallback);
data->handle = NULL;
}
if (data->hostname) {
free(data->hostname);
data->hostname = NULL;
}
if (data->readBuffer) {
free(data->readBuffer);
data->readBuffer = NULL;
}
}
static void tlsAllocCallback(uv_handle_t* handle, size_t suggestedSize, uv_buf_t* buf) {
buf->base = (char*)malloc(suggestedSize);
buf->len = suggestedSize;
}
static void tlsReadCallback(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
TlsSocketData* data = (TlsSocketData*)stream->data;
if (nread < 0) {
uv_read_stop(stream);
free(buf->base);
if (data->fiber) {
WrenHandle* fiber = data->fiber;
data->fiber = NULL;
if (nread == UV_EOF) {
schedulerResume(fiber, true);
wrenSetSlotNull(getVM(), 2);
schedulerFinishResume();
} else {
schedulerResumeError(fiber, uv_strerror(nread));
}
}
return;
}
if (nread > 0) {
BIO_write(data->readBio, buf->base, nread);
}
free(buf->base);
if (data->state == TLS_STATE_HANDSHAKING) {
doHandshake(data);
return;
}
if (data->state == TLS_STATE_CONNECTED && data->wantRead && data->fiber) {
int decrypted = SSL_read(data->ssl, data->readBuffer, data->readBufferSize - 1);
if (decrypted > 0) {
uv_read_stop(stream);
data->wantRead = false;
flushWriteBio(data);
WrenHandle* fiber = data->fiber;
data->fiber = NULL;
schedulerResume(fiber, true);
wrenSetSlotBytes(getVM(), 2, data->readBuffer, decrypted);
schedulerFinishResume();
} else {
int err = SSL_get_error(data->ssl, decrypted);
if (err == SSL_ERROR_ZERO_RETURN) {
uv_read_stop(stream);
data->wantRead = false;
flushWriteBio(data);
WrenHandle* fiber = data->fiber;
data->fiber = NULL;
schedulerResume(fiber, true);
wrenSetSlotNull(getVM(), 2);
schedulerFinishResume();
} else if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) {
uv_read_stop(stream);
data->wantRead = false;
flushWriteBio(data);
WrenHandle* fiber = data->fiber;
data->fiber = NULL;
schedulerResumeError(fiber, "SSL read error");
}
}
}
}
typedef struct {
uv_write_t req;
uv_buf_t buf;
TlsSocketData* data;
bool isLast;
} TlsWriteRequest;
static void tlsWriteCallback(uv_write_t* req, int status) {
TlsWriteRequest* wr = (TlsWriteRequest*)req;
TlsSocketData* data = wr->data;
bool isLast = wr->isLast;
free(wr->buf.base);
free(wr);
if (isLast && data->writeInProgress && data->fiber) {
data->writeInProgress = false;
WrenHandle* fiber = data->fiber;
data->fiber = NULL;
if (status < 0) {
schedulerResumeError(fiber, uv_strerror(status));
} else {
schedulerResume(fiber, false);
}
}
}
static void flushWriteBio(TlsSocketData* data) {
if (data->handle == NULL) return;
char buffer[16384];
int pending;
while ((pending = BIO_read(data->writeBio, buffer, sizeof(buffer))) > 0) {
TlsWriteRequest* wr = (TlsWriteRequest*)malloc(sizeof(TlsWriteRequest));
wr->buf.base = (char*)malloc(pending);
wr->buf.len = pending;
memcpy(wr->buf.base, buffer, pending);
wr->data = data;
wr->isLast = false;
wr->req.data = wr;
uv_write(&wr->req, (uv_stream_t*)data->handle, &wr->buf, 1, tlsWriteCallback);
}
}
static void doHandshake(TlsSocketData* data) {
int result = SSL_do_handshake(data->ssl);
flushWriteBio(data);
if (result == 1) {
data->state = TLS_STATE_CONNECTED;
uv_read_stop((uv_stream_t*)data->handle);
if (data->fiber) {
WrenHandle* fiber = data->fiber;
data->fiber = NULL;
schedulerResume(fiber, false);
}
} else {
int err = SSL_get_error(data->ssl, result);
if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
return;
}
data->state = TLS_STATE_ERROR;
uv_read_stop((uv_stream_t*)data->handle);
if (data->fiber) {
WrenHandle* fiber = data->fiber;
data->fiber = NULL;
unsigned long errCode = ERR_get_error();
char errMsg[256];
ERR_error_string_n(errCode, errMsg, sizeof(errMsg));
schedulerResumeError(fiber, errMsg);
}
}
}
static void tlsConnectCallback(uv_connect_t* req, int status) {
TlsSocketData* data = (TlsSocketData*)req->data;
free(req);
if (status < 0) {
data->state = TLS_STATE_ERROR;
if (data->fiber) {
WrenHandle* fiber = data->fiber;
data->fiber = NULL;
schedulerResumeError(fiber, uv_strerror(status));
}
return;
}
data->state = TLS_STATE_HANDSHAKING;
if (data->hostname) {
SSL_set_tlsext_host_name(data->ssl, data->hostname);
SSL_set1_host(data->ssl, data->hostname);
}
uv_read_start((uv_stream_t*)data->handle, tlsAllocCallback, tlsReadCallback);
doHandshake(data);
}
void tlsSocketConnect(WrenVM* vm) {
TlsSocketData* data = (TlsSocketData*)wrenGetSlotForeign(vm, 0);
const char* host = wrenGetSlotString(vm, 1);
int port = (int)wrenGetSlotDouble(vm, 2);
const char* hostname = wrenGetSlotString(vm, 3);
WrenHandle* fiber = wrenGetSlotHandle(vm, 4);
data->fiber = fiber;
data->hostname = strdup(hostname);
data->state = TLS_STATE_CONNECTING;
struct sockaddr_in addr;
int r = uv_ip4_addr(host, port, &addr);
if (r != 0) {
data->state = TLS_STATE_ERROR;
schedulerResumeError(fiber, "Invalid IP address");
return;
}
uv_connect_t* connectReq = (uv_connect_t*)malloc(sizeof(uv_connect_t));
connectReq->data = data;
r = uv_tcp_connect(connectReq, data->handle, (const struct sockaddr*)&addr, tlsConnectCallback);
if (r != 0) {
free(connectReq);
data->state = TLS_STATE_ERROR;
schedulerResumeError(fiber, uv_strerror(r));
}
}
void tlsSocketWrite(WrenVM* vm) {
TlsSocketData* data = (TlsSocketData*)wrenGetSlotForeign(vm, 0);
int length;
const char* text = wrenGetSlotBytes(vm, 1, &length);
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);
if (data->state != TLS_STATE_CONNECTED) {
schedulerResumeError(fiber, "Socket not connected");
return;
}
data->fiber = fiber;
data->writeInProgress = true;
int written = SSL_write(data->ssl, text, length);
if (written <= 0) {
int err = SSL_get_error(data->ssl, written);
if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) {
data->writeInProgress = false;
data->fiber = NULL;
schedulerResumeError(fiber, "SSL write error");
return;
}
}
char buffer[16384];
int pending;
TlsWriteRequest* lastWr = NULL;
while ((pending = BIO_read(data->writeBio, buffer, sizeof(buffer))) > 0) {
TlsWriteRequest* wr = (TlsWriteRequest*)malloc(sizeof(TlsWriteRequest));
wr->buf.base = (char*)malloc(pending);
wr->buf.len = pending;
memcpy(wr->buf.base, buffer, pending);
wr->data = data;
wr->isLast = false;
wr->req.data = wr;
lastWr = wr;
uv_write(&wr->req, (uv_stream_t*)data->handle, &wr->buf, 1, tlsWriteCallback);
}
if (lastWr) {
lastWr->isLast = true;
} else {
data->writeInProgress = false;
data->fiber = NULL;
schedulerResume(fiber, false);
}
}
void tlsSocketRead(WrenVM* vm) {
TlsSocketData* data = (TlsSocketData*)wrenGetSlotForeign(vm, 0);
WrenHandle* fiber = wrenGetSlotHandle(vm, 1);
if (data->state != TLS_STATE_CONNECTED) {
schedulerResumeError(fiber, "Socket not connected");
return;
}
data->fiber = fiber;
data->wantRead = true;
uv_read_start((uv_stream_t*)data->handle, tlsAllocCallback, tlsReadCallback);
}
void tlsSocketClose(WrenVM* vm) {
TlsSocketData* data = (TlsSocketData*)wrenGetSlotForeign(vm, 0);
if (data->ssl) {
SSL_shutdown(data->ssl);
}
if (data->handle && !uv_is_closing((uv_handle_t*)data->handle)) {
uv_close((uv_handle_t*)data->handle, tlsCloseCallback);
data->handle = NULL;
}
data->state = TLS_STATE_DISCONNECTED;
}
+15
View File
@@ -0,0 +1,15 @@
// retoor <retoor@molodetz.nl>
#ifndef tls_h
#define tls_h
#include "wren.h"
void tlsSocketAllocate(WrenVM* vm);
void tlsSocketFinalize(void* data);
void tlsSocketConnect(WrenVM* vm);
void tlsSocketWrite(WrenVM* vm);
void tlsSocketRead(WrenVM* vm);
void tlsSocketClose(WrenVM* vm);
#endif
+30
View File
@@ -0,0 +1,30 @@
// retoor <retoor@molodetz.nl>
import "scheduler" for Scheduler
foreign class TlsSocket {
construct new() {}
foreign connect_(host, port, hostname, fiber)
foreign write_(text, fiber)
foreign read_(fiber)
foreign close_()
static connect(host, port, hostname) {
var socket = TlsSocket.new()
Scheduler.await_ { socket.connect_(host, port, hostname, Fiber.current) }
return socket
}
write(text) {
Scheduler.await_ { write_(text, Fiber.current) }
}
read() {
return Scheduler.await_ { read_(Fiber.current) }
}
close() {
close_()
}
}
+34
View File
@@ -0,0 +1,34 @@
// Please do not edit this file. It has been generated automatically
// from `/home/retoor/projects/wren-cli/src/module/tls.wren` using `util/wren_to_c_string.py`
static const char* tlsModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"import \"scheduler\" for Scheduler\n"
"\n"
"foreign class TlsSocket {\n"
" construct new() {}\n"
"\n"
" foreign connect_(host, port, hostname, fiber)\n"
" foreign write_(text, fiber)\n"
" foreign read_(fiber)\n"
" foreign close_()\n"
"\n"
" static connect(host, port, hostname) {\n"
" var socket = TlsSocket.new()\n"
" Scheduler.await_ { socket.connect_(host, port, hostname, Fiber.current) }\n"
" return socket\n"
" }\n"
"\n"
" write(text) {\n"
" Scheduler.await_ { write_(text, Fiber.current) }\n"
" }\n"
"\n"
" read() {\n"
" return Scheduler.await_ { read_(Fiber.current) }\n"
" }\n"
"\n"
" close() {\n"
" close_()\n"
" }\n"
"}\n";
+553
View File
@@ -0,0 +1,553 @@
// retoor <retoor@molodetz.nl>
import "net" for Socket, Server
import "tls" for TlsSocket
import "dns" for Dns
import "crypto" for Crypto, Hash
import "base64" for Base64
class WebSocketMessage {
construct new_(opcode, payload, fin) {
_opcode = opcode
_payload = payload
_fin = fin
}
opcode { _opcode }
payload { _payload }
fin { _fin }
isText { _opcode == 1 }
isBinary { _opcode == 2 }
isClose { _opcode == 8 }
isPing { _opcode == 9 }
isPong { _opcode == 10 }
text {
if (!isText) Fiber.abort("Message is not a text frame.")
return bytesToString_(_payload)
}
bytes { _payload }
closeCode {
if (!isClose) Fiber.abort("Message is not a close frame.")
if (_payload.count < 2) return 1005
return (_payload[0] << 8) | _payload[1]
}
closeReason {
if (!isClose) Fiber.abort("Message is not a close frame.")
if (_payload.count <= 2) return ""
return bytesToString_(_payload[2..-1])
}
static bytesToString_(bytes) {
var str = ""
for (b in bytes) {
str = str + String.fromCodePoint(b)
}
return str
}
bytesToString_(bytes) { WebSocketMessage.bytesToString_(bytes) }
}
class WebSocket {
construct new_(socket, url, isClient) {
_socket = socket
_url = url
_isClient = isClient
_isOpen = true
_fragmentBuffer = []
_fragmentOpcode = null
_readBuffer = []
}
static connect(url) { connect(url, {}) }
static connect(url, headers) {
var parsed = parseUrl_(url)
if (parsed["scheme"] != "ws" && parsed["scheme"] != "wss") {
Fiber.abort("Unsupported scheme: %(parsed["scheme"]). Use ws:// or wss://")
}
var isSecure = parsed["scheme"] == "wss"
var host = parsed["host"]
var port = parsed["port"]
var path = parsed["path"]
var addresses = Dns.lookup(host, 4)
if (addresses.count == 0) {
Fiber.abort("Could not resolve host: %(host)")
}
var socket
if (isSecure) {
socket = TlsSocket.connect(addresses[0], port, host)
} else {
socket = Socket.connect(addresses[0], port)
}
var keyBytes = Crypto.randomBytes(16)
var key = Base64.encode(bytesToString_(keyBytes))
var request = "GET %(path) HTTP/1.1\r\n"
request = request + "Host: %(host)\r\n"
request = request + "Upgrade: websocket\r\n"
request = request + "Connection: Upgrade\r\n"
request = request + "Sec-WebSocket-Key: %(key)\r\n"
request = request + "Sec-WebSocket-Version: 13\r\n"
for (entry in headers) {
request = request + "%(entry.key): %(entry.value)\r\n"
}
request = request + "\r\n"
socket.write(request)
var response = ""
while (true) {
var chunk = socket.read()
if (chunk == null || chunk.count == 0) {
Fiber.abort("Connection closed during handshake")
}
response = response + chunk
if (response.indexOf("\r\n\r\n") >= 0) break
}
var headerEnd = response.indexOf("\r\n\r\n")
var headerPart = response[0...headerEnd]
var lines = splitLines_(headerPart)
if (lines.count == 0) {
Fiber.abort("Invalid WebSocket handshake response")
}
var statusLine = lines[0]
if (!statusLine.contains("101")) {
Fiber.abort("WebSocket handshake failed: %(statusLine)")
}
var expectedAccept = computeAcceptKey_(key)
var receivedAccept = null
for (i in 1...lines.count) {
var line = lines[i]
var colonPos = line.indexOf(":")
if (colonPos > 0) {
var name = toLower_(line[0...colonPos].trim())
var value = line[(colonPos + 1)..-1].trim()
if (name == "sec-websocket-accept") {
receivedAccept = value
break
}
}
}
if (receivedAccept != expectedAccept) {
Fiber.abort("Invalid Sec-WebSocket-Accept header")
}
return WebSocket.new_(socket, url, true)
}
static parseUrl_(url) {
var result = {
"scheme": "ws",
"host": "",
"port": 80,
"path": "/"
}
var rest = url
var schemeEnd = rest.indexOf("://")
if (schemeEnd >= 0) {
result["scheme"] = toLower_(rest[0...schemeEnd])
rest = rest[(schemeEnd + 3)..-1]
if (result["scheme"] == "wss") result["port"] = 443
}
var pathStart = rest.indexOf("/")
var hostPart = pathStart >= 0 ? rest[0...pathStart] : rest
result["path"] = pathStart >= 0 ? rest[pathStart..-1] : "/"
var portStart = hostPart.indexOf(":")
if (portStart >= 0) {
result["host"] = hostPart[0...portStart]
result["port"] = Num.fromString(hostPart[(portStart + 1)..-1])
} else {
result["host"] = hostPart
}
return result
}
static splitLines_(text) {
var lines = []
var start = 0
var i = 0
while (i < text.count) {
if (text[i] == "\r" && i + 1 < text.count && text[i + 1] == "\n") {
lines.add(text[start...i])
start = i + 2
i = i + 2
} else {
i = i + 1
}
}
if (start < text.count) {
lines.add(text[start..-1])
}
return lines
}
static toLower_(str) {
var result = ""
for (c in str) {
var cp = c.codePoints[0]
if (cp >= 65 && cp <= 90) {
result = result + String.fromCodePoint(cp + 32)
} else {
result = result + c
}
}
return result
}
static computeAcceptKey_(key) {
var magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
var combined = key + magic
var sha1 = Hash.sha1(combined)
var sha1Str = bytesToString_(sha1)
return Base64.encode(sha1Str)
}
static bytesToString_(bytes) {
var str = ""
for (b in bytes) {
str = str + String.fromByte(b)
}
return str
}
url { _url }
isOpen { _isOpen }
send(text) {
if (!_isOpen) Fiber.abort("WebSocket is not open.")
if (!(text is String)) Fiber.abort("Data must be a string.")
var payload = stringToBytes_(text)
sendFrame_(1, payload)
}
sendBinary(bytes) {
if (!_isOpen) Fiber.abort("WebSocket is not open.")
if (!(bytes is List)) Fiber.abort("Data must be a list of bytes.")
sendFrame_(2, bytes)
}
ping() { ping([]) }
ping(data) {
if (!_isOpen) Fiber.abort("WebSocket is not open.")
var payload = data
if (data is String) payload = stringToBytes_(data)
if (payload.count > 125) Fiber.abort("Ping payload too large (max 125 bytes).")
sendFrame_(9, payload)
}
pong(data) {
if (!_isOpen) Fiber.abort("WebSocket is not open.")
var payload = data
if (data is String) payload = stringToBytes_(data)
if (payload.count > 125) Fiber.abort("Pong payload too large (max 125 bytes).")
sendFrame_(10, payload)
}
close() { close(1000, "") }
close(code, reason) {
if (!_isOpen) return
var payload = []
payload.add((code >> 8) & 0xFF)
payload.add(code & 0xFF)
for (b in stringToBytes_(reason)) {
payload.add(b)
}
sendFrame_(8, payload)
_isOpen = false
_socket.close()
}
receive() {
if (!_isOpen) return null
while (true) {
var frame = readFrame_()
if (frame == null) {
_isOpen = false
return null
}
var opcode = frame.opcode
var payload = frame.payload
var fin = frame.fin
if (opcode == 8) {
_isOpen = false
if (_isClient) {
sendFrame_(8, payload)
_socket.close()
}
return frame
}
if (opcode == 9) {
sendFrame_(10, payload)
continue
}
if (opcode == 10) {
continue
}
if (opcode == 0) {
for (b in payload) {
_fragmentBuffer.add(b)
}
if (fin) {
var completePayload = _fragmentBuffer
var completeOpcode = _fragmentOpcode
_fragmentBuffer = []
_fragmentOpcode = null
return WebSocketMessage.new_(completeOpcode, completePayload, true)
}
continue
}
if (opcode == 1 || opcode == 2) {
if (fin) {
return frame
} else {
_fragmentOpcode = opcode
_fragmentBuffer = []
for (b in payload) {
_fragmentBuffer.add(b)
}
continue
}
}
Fiber.abort("Unknown opcode: %(opcode)")
}
}
sendFrame_(opcode, payload) {
var frame = encodeFrame_(opcode, payload, _isClient)
var data = ""
for (b in frame) {
data = data + String.fromByte(b)
}
_socket.write(data)
}
encodeFrame_(opcode, payload, masked) {
var frame = []
frame.add(0x80 | opcode)
var len = payload.count
var maskBit = masked ? 0x80 : 0x00
if (len < 126) {
frame.add(maskBit | len)
} else if (len < 65536) {
frame.add(maskBit | 126)
frame.add((len >> 8) & 0xFF)
frame.add(len & 0xFF)
} else {
frame.add(maskBit | 127)
for (i in 0...4) frame.add(0)
frame.add((len >> 24) & 0xFF)
frame.add((len >> 16) & 0xFF)
frame.add((len >> 8) & 0xFF)
frame.add(len & 0xFF)
}
if (masked) {
var mask = Crypto.randomBytes(4)
for (b in mask) frame.add(b)
for (i in 0...len) {
frame.add(payload[i] ^ mask[i % 4])
}
} else {
for (b in payload) frame.add(b)
}
return frame
}
readFrame_() {
var header = readBytes_(2)
if (header == null || header.count < 2) return null
var fin = (header[0] & 0x80) != 0
var opcode = header[0] & 0x0F
var masked = (header[1] & 0x80) != 0
var len = header[1] & 0x7F
if (len == 126) {
var ext = readBytes_(2)
if (ext == null || ext.count < 2) return null
len = (ext[0] << 8) | ext[1]
} else if (len == 127) {
var ext = readBytes_(8)
if (ext == null || ext.count < 8) return null
len = 0
for (i in 4...8) {
len = (len << 8) | ext[i]
}
}
var mask = null
if (masked) {
mask = readBytes_(4)
if (mask == null || mask.count < 4) return null
}
var payload = []
if (len > 0) {
payload = readBytes_(len)
if (payload == null) return null
}
if (masked && mask != null) {
for (i in 0...payload.count) {
payload[i] = payload[i] ^ mask[i % 4]
}
}
return WebSocketMessage.new_(opcode, payload, fin)
}
readBytes_(count) {
while (_readBuffer.count < count) {
var chunk = _socket.read()
if (chunk == null || chunk.count == 0) {
if (_readBuffer.count == 0) return null
var result = []
for (b in _readBuffer) result.add(b)
_readBuffer = []
return result
}
for (b in chunk.bytes) {
_readBuffer.add(b)
}
}
var result = []
for (i in 0...count) {
result.add(_readBuffer[i])
}
var remaining = []
for (i in count..._readBuffer.count) {
remaining.add(_readBuffer[i])
}
_readBuffer = remaining
return result
}
stringToBytes_(str) {
var bytes = []
for (b in str.bytes) {
bytes.add(b)
}
return bytes
}
}
class WebSocketServer {
construct new_(server) {
_server = server
}
static bind(host, port) {
var server = Server.bind(host, port)
return WebSocketServer.new_(server)
}
accept() {
var socket = _server.accept()
return upgradeConnection_(socket)
}
upgradeConnection_(socket) {
var request = ""
while (true) {
var chunk = socket.read()
if (chunk == null || chunk.count == 0) {
socket.close()
return null
}
request = request + chunk
if (request.indexOf("\r\n\r\n") >= 0) break
}
var headerEnd = request.indexOf("\r\n\r\n")
var headerPart = request[0...headerEnd]
var lines = WebSocket.splitLines_(headerPart)
if (lines.count == 0) {
socket.close()
return null
}
var key = null
var isUpgrade = false
var isWebSocket = false
for (i in 1...lines.count) {
var line = lines[i]
var colonPos = line.indexOf(":")
if (colonPos > 0) {
var name = WebSocket.toLower_(line[0...colonPos].trim())
var value = line[(colonPos + 1)..-1].trim()
if (name == "sec-websocket-key") {
key = value
} else if (name == "upgrade" && WebSocket.toLower_(value) == "websocket") {
isWebSocket = true
} else if (name == "connection" && WebSocket.toLower_(value).contains("upgrade")) {
isUpgrade = true
}
}
}
if (!isUpgrade || !isWebSocket || key == null) {
var response = "HTTP/1.1 400 Bad Request\r\n\r\n"
socket.write(response)
socket.close()
return null
}
var acceptKey = WebSocket.computeAcceptKey_(key)
var response = "HTTP/1.1 101 Switching Protocols\r\n"
response = response + "Upgrade: websocket\r\n"
response = response + "Connection: Upgrade\r\n"
response = response + "Sec-WebSocket-Accept: %(acceptKey)\r\n"
response = response + "\r\n"
socket.write(response)
return WebSocket.new_(socket, null, false)
}
close() {
_server.close()
}
}
+557
View File
@@ -0,0 +1,557 @@
// Please do not edit this file. It has been generated automatically
// from `src/module/websocket.wren` using `util/wren_to_c_string.py`
static const char* websocketModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"import \"net\" for Socket, Server\n"
"import \"tls\" for TlsSocket\n"
"import \"dns\" for Dns\n"
"import \"crypto\" for Crypto, Hash\n"
"import \"base64\" for Base64\n"
"\n"
"class WebSocketMessage {\n"
" construct new_(opcode, payload, fin) {\n"
" _opcode = opcode\n"
" _payload = payload\n"
" _fin = fin\n"
" }\n"
"\n"
" opcode { _opcode }\n"
" payload { _payload }\n"
" fin { _fin }\n"
"\n"
" isText { _opcode == 1 }\n"
" isBinary { _opcode == 2 }\n"
" isClose { _opcode == 8 }\n"
" isPing { _opcode == 9 }\n"
" isPong { _opcode == 10 }\n"
"\n"
" text {\n"
" if (!isText) Fiber.abort(\"Message is not a text frame.\")\n"
" return bytesToString_(_payload)\n"
" }\n"
"\n"
" bytes { _payload }\n"
"\n"
" closeCode {\n"
" if (!isClose) Fiber.abort(\"Message is not a close frame.\")\n"
" if (_payload.count < 2) return 1005\n"
" return (_payload[0] << 8) | _payload[1]\n"
" }\n"
"\n"
" closeReason {\n"
" if (!isClose) Fiber.abort(\"Message is not a close frame.\")\n"
" if (_payload.count <= 2) return \"\"\n"
" return bytesToString_(_payload[2..-1])\n"
" }\n"
"\n"
" static bytesToString_(bytes) {\n"
" var str = \"\"\n"
" for (b in bytes) {\n"
" str = str + String.fromCodePoint(b)\n"
" }\n"
" return str\n"
" }\n"
"\n"
" bytesToString_(bytes) { WebSocketMessage.bytesToString_(bytes) }\n"
"}\n"
"\n"
"class WebSocket {\n"
" construct new_(socket, url, isClient) {\n"
" _socket = socket\n"
" _url = url\n"
" _isClient = isClient\n"
" _isOpen = true\n"
" _fragmentBuffer = []\n"
" _fragmentOpcode = null\n"
" _readBuffer = []\n"
" }\n"
"\n"
" static connect(url) { connect(url, {}) }\n"
"\n"
" static connect(url, headers) {\n"
" var parsed = parseUrl_(url)\n"
"\n"
" if (parsed[\"scheme\"] != \"ws\" && parsed[\"scheme\"] != \"wss\") {\n"
" Fiber.abort(\"Unsupported scheme: %(parsed[\"scheme\"]). Use ws:// or wss://\")\n"
" }\n"
"\n"
" var isSecure = parsed[\"scheme\"] == \"wss\"\n"
" var host = parsed[\"host\"]\n"
" var port = parsed[\"port\"]\n"
" var path = parsed[\"path\"]\n"
"\n"
" var addresses = Dns.lookup(host, 4)\n"
" if (addresses.count == 0) {\n"
" Fiber.abort(\"Could not resolve host: %(host)\")\n"
" }\n"
"\n"
" var socket\n"
" if (isSecure) {\n"
" socket = TlsSocket.connect(addresses[0], port, host)\n"
" } else {\n"
" socket = Socket.connect(addresses[0], port)\n"
" }\n"
"\n"
" var keyBytes = Crypto.randomBytes(16)\n"
" var key = Base64.encode(bytesToString_(keyBytes))\n"
"\n"
" var request = \"GET %(path) HTTP/1.1\\r\\n\"\n"
" request = request + \"Host: %(host)\\r\\n\"\n"
" request = request + \"Upgrade: websocket\\r\\n\"\n"
" request = request + \"Connection: Upgrade\\r\\n\"\n"
" request = request + \"Sec-WebSocket-Key: %(key)\\r\\n\"\n"
" request = request + \"Sec-WebSocket-Version: 13\\r\\n\"\n"
"\n"
" for (entry in headers) {\n"
" request = request + \"%(entry.key): %(entry.value)\\r\\n\"\n"
" }\n"
" request = request + \"\\r\\n\"\n"
"\n"
" socket.write(request)\n"
"\n"
" var response = \"\"\n"
" while (true) {\n"
" var chunk = socket.read()\n"
" if (chunk == null || chunk.count == 0) {\n"
" Fiber.abort(\"Connection closed during handshake\")\n"
" }\n"
" response = response + chunk\n"
" if (response.indexOf(\"\\r\\n\\r\\n\") >= 0) break\n"
" }\n"
"\n"
" var headerEnd = response.indexOf(\"\\r\\n\\r\\n\")\n"
" var headerPart = response[0...headerEnd]\n"
" var lines = splitLines_(headerPart)\n"
"\n"
" if (lines.count == 0) {\n"
" Fiber.abort(\"Invalid WebSocket handshake response\")\n"
" }\n"
"\n"
" var statusLine = lines[0]\n"
" if (!statusLine.contains(\"101\")) {\n"
" Fiber.abort(\"WebSocket handshake failed: %(statusLine)\")\n"
" }\n"
"\n"
" var expectedAccept = computeAcceptKey_(key)\n"
" var receivedAccept = null\n"
"\n"
" for (i in 1...lines.count) {\n"
" var line = lines[i]\n"
" var colonPos = line.indexOf(\":\")\n"
" if (colonPos > 0) {\n"
" var name = toLower_(line[0...colonPos].trim())\n"
" var value = line[(colonPos + 1)..-1].trim()\n"
" if (name == \"sec-websocket-accept\") {\n"
" receivedAccept = value\n"
" break\n"
" }\n"
" }\n"
" }\n"
"\n"
" if (receivedAccept != expectedAccept) {\n"
" Fiber.abort(\"Invalid Sec-WebSocket-Accept header\")\n"
" }\n"
"\n"
" return WebSocket.new_(socket, url, true)\n"
" }\n"
"\n"
" static parseUrl_(url) {\n"
" var result = {\n"
" \"scheme\": \"ws\",\n"
" \"host\": \"\",\n"
" \"port\": 80,\n"
" \"path\": \"/\"\n"
" }\n"
"\n"
" var rest = url\n"
"\n"
" var schemeEnd = rest.indexOf(\"://\")\n"
" if (schemeEnd >= 0) {\n"
" result[\"scheme\"] = toLower_(rest[0...schemeEnd])\n"
" rest = rest[(schemeEnd + 3)..-1]\n"
" if (result[\"scheme\"] == \"wss\") result[\"port\"] = 443\n"
" }\n"
"\n"
" var pathStart = rest.indexOf(\"/\")\n"
" var hostPart = pathStart >= 0 ? rest[0...pathStart] : rest\n"
" result[\"path\"] = pathStart >= 0 ? rest[pathStart..-1] : \"/\"\n"
"\n"
" var portStart = hostPart.indexOf(\":\")\n"
" if (portStart >= 0) {\n"
" result[\"host\"] = hostPart[0...portStart]\n"
" result[\"port\"] = Num.fromString(hostPart[(portStart + 1)..-1])\n"
" } else {\n"
" result[\"host\"] = hostPart\n"
" }\n"
"\n"
" return result\n"
" }\n"
"\n"
" static splitLines_(text) {\n"
" var lines = []\n"
" var start = 0\n"
" var i = 0\n"
" while (i < text.count) {\n"
" if (text[i] == \"\\r\" && i + 1 < text.count && text[i + 1] == \"\\n\") {\n"
" lines.add(text[start...i])\n"
" start = i + 2\n"
" i = i + 2\n"
" } else {\n"
" i = i + 1\n"
" }\n"
" }\n"
" if (start < text.count) {\n"
" lines.add(text[start..-1])\n"
" }\n"
" return lines\n"
" }\n"
"\n"
" static toLower_(str) {\n"
" var result = \"\"\n"
" for (c in str) {\n"
" var cp = c.codePoints[0]\n"
" if (cp >= 65 && cp <= 90) {\n"
" result = result + String.fromCodePoint(cp + 32)\n"
" } else {\n"
" result = result + c\n"
" }\n"
" }\n"
" return result\n"
" }\n"
"\n"
" static computeAcceptKey_(key) {\n"
" var magic = \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\"\n"
" var combined = key + magic\n"
" var sha1 = Hash.sha1(combined)\n"
" var sha1Str = bytesToString_(sha1)\n"
" return Base64.encode(sha1Str)\n"
" }\n"
"\n"
" static bytesToString_(bytes) {\n"
" var str = \"\"\n"
" for (b in bytes) {\n"
" str = str + String.fromByte(b)\n"
" }\n"
" return str\n"
" }\n"
"\n"
" url { _url }\n"
" isOpen { _isOpen }\n"
"\n"
" send(text) {\n"
" if (!_isOpen) Fiber.abort(\"WebSocket is not open.\")\n"
" if (!(text is String)) Fiber.abort(\"Data must be a string.\")\n"
" var payload = stringToBytes_(text)\n"
" sendFrame_(1, payload)\n"
" }\n"
"\n"
" sendBinary(bytes) {\n"
" if (!_isOpen) Fiber.abort(\"WebSocket is not open.\")\n"
" if (!(bytes is List)) Fiber.abort(\"Data must be a list of bytes.\")\n"
" sendFrame_(2, bytes)\n"
" }\n"
"\n"
" ping() { ping([]) }\n"
"\n"
" ping(data) {\n"
" if (!_isOpen) Fiber.abort(\"WebSocket is not open.\")\n"
" var payload = data\n"
" if (data is String) payload = stringToBytes_(data)\n"
" if (payload.count > 125) Fiber.abort(\"Ping payload too large (max 125 bytes).\")\n"
" sendFrame_(9, payload)\n"
" }\n"
"\n"
" pong(data) {\n"
" if (!_isOpen) Fiber.abort(\"WebSocket is not open.\")\n"
" var payload = data\n"
" if (data is String) payload = stringToBytes_(data)\n"
" if (payload.count > 125) Fiber.abort(\"Pong payload too large (max 125 bytes).\")\n"
" sendFrame_(10, payload)\n"
" }\n"
"\n"
" close() { close(1000, \"\") }\n"
"\n"
" close(code, reason) {\n"
" if (!_isOpen) return\n"
"\n"
" var payload = []\n"
" payload.add((code >> 8) & 0xFF)\n"
" payload.add(code & 0xFF)\n"
" for (b in stringToBytes_(reason)) {\n"
" payload.add(b)\n"
" }\n"
"\n"
" sendFrame_(8, payload)\n"
" _isOpen = false\n"
" _socket.close()\n"
" }\n"
"\n"
" receive() {\n"
" if (!_isOpen) return null\n"
"\n"
" while (true) {\n"
" var frame = readFrame_()\n"
" if (frame == null) {\n"
" _isOpen = false\n"
" return null\n"
" }\n"
"\n"
" var opcode = frame.opcode\n"
" var payload = frame.payload\n"
" var fin = frame.fin\n"
"\n"
" if (opcode == 8) {\n"
" _isOpen = false\n"
" if (_isClient) {\n"
" sendFrame_(8, payload)\n"
" _socket.close()\n"
" }\n"
" return frame\n"
" }\n"
"\n"
" if (opcode == 9) {\n"
" sendFrame_(10, payload)\n"
" continue\n"
" }\n"
"\n"
" if (opcode == 10) {\n"
" continue\n"
" }\n"
"\n"
" if (opcode == 0) {\n"
" for (b in payload) {\n"
" _fragmentBuffer.add(b)\n"
" }\n"
" if (fin) {\n"
" var completePayload = _fragmentBuffer\n"
" var completeOpcode = _fragmentOpcode\n"
" _fragmentBuffer = []\n"
" _fragmentOpcode = null\n"
" return WebSocketMessage.new_(completeOpcode, completePayload, true)\n"
" }\n"
" continue\n"
" }\n"
"\n"
" if (opcode == 1 || opcode == 2) {\n"
" if (fin) {\n"
" return frame\n"
" } else {\n"
" _fragmentOpcode = opcode\n"
" _fragmentBuffer = []\n"
" for (b in payload) {\n"
" _fragmentBuffer.add(b)\n"
" }\n"
" continue\n"
" }\n"
" }\n"
"\n"
" Fiber.abort(\"Unknown opcode: %(opcode)\")\n"
" }\n"
" }\n"
"\n"
" sendFrame_(opcode, payload) {\n"
" var frame = encodeFrame_(opcode, payload, _isClient)\n"
" var data = \"\"\n"
" for (b in frame) {\n"
" data = data + String.fromByte(b)\n"
" }\n"
" _socket.write(data)\n"
" }\n"
"\n"
" encodeFrame_(opcode, payload, masked) {\n"
" var frame = []\n"
"\n"
" frame.add(0x80 | opcode)\n"
"\n"
" var len = payload.count\n"
" var maskBit = masked ? 0x80 : 0x00\n"
"\n"
" if (len < 126) {\n"
" frame.add(maskBit | len)\n"
" } else if (len < 65536) {\n"
" frame.add(maskBit | 126)\n"
" frame.add((len >> 8) & 0xFF)\n"
" frame.add(len & 0xFF)\n"
" } else {\n"
" frame.add(maskBit | 127)\n"
" for (i in 0...4) frame.add(0)\n"
" frame.add((len >> 24) & 0xFF)\n"
" frame.add((len >> 16) & 0xFF)\n"
" frame.add((len >> 8) & 0xFF)\n"
" frame.add(len & 0xFF)\n"
" }\n"
"\n"
" if (masked) {\n"
" var mask = Crypto.randomBytes(4)\n"
" for (b in mask) frame.add(b)\n"
" for (i in 0...len) {\n"
" frame.add(payload[i] ^ mask[i % 4])\n"
" }\n"
" } else {\n"
" for (b in payload) frame.add(b)\n"
" }\n"
"\n"
" return frame\n"
" }\n"
"\n"
" readFrame_() {\n"
" var header = readBytes_(2)\n"
" if (header == null || header.count < 2) return null\n"
"\n"
" var fin = (header[0] & 0x80) != 0\n"
" var opcode = header[0] & 0x0F\n"
" var masked = (header[1] & 0x80) != 0\n"
" var len = header[1] & 0x7F\n"
"\n"
" if (len == 126) {\n"
" var ext = readBytes_(2)\n"
" if (ext == null || ext.count < 2) return null\n"
" len = (ext[0] << 8) | ext[1]\n"
" } else if (len == 127) {\n"
" var ext = readBytes_(8)\n"
" if (ext == null || ext.count < 8) return null\n"
" len = 0\n"
" for (i in 4...8) {\n"
" len = (len << 8) | ext[i]\n"
" }\n"
" }\n"
"\n"
" var mask = null\n"
" if (masked) {\n"
" mask = readBytes_(4)\n"
" if (mask == null || mask.count < 4) return null\n"
" }\n"
"\n"
" var payload = []\n"
" if (len > 0) {\n"
" payload = readBytes_(len)\n"
" if (payload == null) return null\n"
" }\n"
"\n"
" if (masked && mask != null) {\n"
" for (i in 0...payload.count) {\n"
" payload[i] = payload[i] ^ mask[i % 4]\n"
" }\n"
" }\n"
"\n"
" return WebSocketMessage.new_(opcode, payload, fin)\n"
" }\n"
"\n"
" readBytes_(count) {\n"
" while (_readBuffer.count < count) {\n"
" var chunk = _socket.read()\n"
" if (chunk == null || chunk.count == 0) {\n"
" if (_readBuffer.count == 0) return null\n"
" var result = []\n"
" for (b in _readBuffer) result.add(b)\n"
" _readBuffer = []\n"
" return result\n"
" }\n"
" for (b in chunk.bytes) {\n"
" _readBuffer.add(b)\n"
" }\n"
" }\n"
" var result = []\n"
" for (i in 0...count) {\n"
" result.add(_readBuffer[i])\n"
" }\n"
" var remaining = []\n"
" for (i in count..._readBuffer.count) {\n"
" remaining.add(_readBuffer[i])\n"
" }\n"
" _readBuffer = remaining\n"
" return result\n"
" }\n"
"\n"
" stringToBytes_(str) {\n"
" var bytes = []\n"
" for (b in str.bytes) {\n"
" bytes.add(b)\n"
" }\n"
" return bytes\n"
" }\n"
"}\n"
"\n"
"class WebSocketServer {\n"
" construct new_(server) {\n"
" _server = server\n"
" }\n"
"\n"
" static bind(host, port) {\n"
" var server = Server.bind(host, port)\n"
" return WebSocketServer.new_(server)\n"
" }\n"
"\n"
" accept() {\n"
" var socket = _server.accept()\n"
" return upgradeConnection_(socket)\n"
" }\n"
"\n"
" upgradeConnection_(socket) {\n"
" var request = \"\"\n"
" while (true) {\n"
" var chunk = socket.read()\n"
" if (chunk == null || chunk.count == 0) {\n"
" socket.close()\n"
" return null\n"
" }\n"
" request = request + chunk\n"
" if (request.indexOf(\"\\r\\n\\r\\n\") >= 0) break\n"
" }\n"
"\n"
" var headerEnd = request.indexOf(\"\\r\\n\\r\\n\")\n"
" var headerPart = request[0...headerEnd]\n"
" var lines = WebSocket.splitLines_(headerPart)\n"
"\n"
" if (lines.count == 0) {\n"
" socket.close()\n"
" return null\n"
" }\n"
"\n"
" var key = null\n"
" var isUpgrade = false\n"
" var isWebSocket = false\n"
"\n"
" for (i in 1...lines.count) {\n"
" var line = lines[i]\n"
" var colonPos = line.indexOf(\":\")\n"
" if (colonPos > 0) {\n"
" var name = WebSocket.toLower_(line[0...colonPos].trim())\n"
" var value = line[(colonPos + 1)..-1].trim()\n"
"\n"
" if (name == \"sec-websocket-key\") {\n"
" key = value\n"
" } else if (name == \"upgrade\" && WebSocket.toLower_(value) == \"websocket\") {\n"
" isWebSocket = true\n"
" } else if (name == \"connection\" && WebSocket.toLower_(value).contains(\"upgrade\")) {\n"
" isUpgrade = true\n"
" }\n"
" }\n"
" }\n"
"\n"
" if (!isUpgrade || !isWebSocket || key == null) {\n"
" var response = \"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\"\n"
" socket.write(response)\n"
" socket.close()\n"
" return null\n"
" }\n"
"\n"
" var acceptKey = WebSocket.computeAcceptKey_(key)\n"
"\n"
" var response = \"HTTP/1.1 101 Switching Protocols\\r\\n\"\n"
" response = response + \"Upgrade: websocket\\r\\n\"\n"
" response = response + \"Connection: Upgrade\\r\\n\"\n"
" response = response + \"Sec-WebSocket-Accept: %(acceptKey)\\r\\n\"\n"
" response = response + \"\\r\\n\"\n"
"\n"
" socket.write(response)\n"
"\n"
" return WebSocket.new_(socket, null, false)\n"
" }\n"
"\n"
" close() {\n"
" _server.close()\n"
" }\n"
"}\n";