New modules.
This commit is contained in:
+204
-1
@@ -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
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
Vendored
+28
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
Vendored
+63
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
Vendored
+113
@@ -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" }
|
||||
}
|
||||
@@ -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";
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef wren_dns_h
|
||||
#define wren_dns_h
|
||||
|
||||
#include "wren.h"
|
||||
|
||||
void dnsLookup(WrenVM* vm);
|
||||
|
||||
#endif
|
||||
Vendored
+17
@@ -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)
|
||||
}
|
||||
@@ -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";
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
Vendored
+8
@@ -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
|
||||
}
|
||||
@@ -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";
|
||||
Vendored
+304
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
Vendored
+2649
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef wren_json_h
|
||||
#define wren_json_h
|
||||
|
||||
#include "wren.h"
|
||||
|
||||
void jsonParse(WrenVM* vm);
|
||||
|
||||
#endif
|
||||
Vendored
+99
@@ -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 + "}"
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
Vendored
+46
@@ -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 }
|
||||
}
|
||||
@@ -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";
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
Vendored
+36
@@ -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 }
|
||||
}
|
||||
@@ -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";
|
||||
Vendored
+35
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
Vendored
+14
@@ -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
|
||||
}
|
||||
@@ -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";
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef wren_subprocess_h
|
||||
#define wren_subprocess_h
|
||||
|
||||
#include "wren.h"
|
||||
|
||||
void subprocessRun(WrenVM* vm);
|
||||
|
||||
#endif
|
||||
Vendored
+47
@@ -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])
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
Vendored
+30
@@ -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_()
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
Vendored
+553
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
Reference in New Issue
Block a user