diff --git a/.github/workflows/smoke-test.yaml b/.github/workflows/smoke-test.yaml new file mode 100644 index 0000000000..405737a28f --- /dev/null +++ b/.github/workflows/smoke-test.yaml @@ -0,0 +1,32 @@ +name: build and smoke test moddable SDK for linux CLI (x-cli-lin) + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - name: Install libgtk-3-dev + run: | + sudo apt-get -y update + sudo apt-get -y install libgtk-3-dev + - name: build moddable headless tools + run: | + export MODDABLE=$PWD + export XS_DIR=$MODDABLE/xs + cd $MODDABLE/build/makefiles/lin + make headless + - name: check that helloworld builds + run: | + export MODDABLE=$PWD + export PATH=$MODDABLE/build/bin/lin/release:$PATH + cd $MODDABLE/examples/helloworld + mcconfig -m -p x-cli-lin + # ISSUE: support scripts with no main()? + # $MODDABLE/build/bin/lin/release/helloworld + test -x $MODDABLE/build/bin/lin/release/helloworld + mcconfig -d -m -p x-cli-lin + test -x $MODDABLE/build/bin/lin/debug/helloworld diff --git a/build/makefiles/lin/makefile b/build/makefiles/lin/makefile index daae0d91d2..972ddd52c3 100644 --- a/build/makefiles/lin/makefile +++ b/build/makefiles/lin/makefile @@ -42,7 +42,13 @@ release: make GOAL=release -f simulator.mk make GOAL=release -f tools.mk $(MODDABLE)/build/bin/lin/release/mcconfig -m -p x-lin $(MODDABLE)/tools/xsbug/manifest.json - + +headless: + make GOAL=release -f $(XS_DIR)/makefiles/lin/xsc.mk + make GOAL=release -f $(XS_DIR)/makefiles/lin/xsid.mk + make GOAL=release -f $(XS_DIR)/makefiles/lin/xsl.mk + make GOAL=release -f tools.mk + clean: make clean -f $(XS_DIR)/makefiles/lin/xsc.mk make clean -f $(XS_DIR)/makefiles/lin/xsid.mk diff --git a/examples/cli/main.js b/examples/cli/main.js new file mode 100644 index 0000000000..84021a86e5 --- /dev/null +++ b/examples/cli/main.js @@ -0,0 +1,11 @@ +trace("top-level executes\n"); + +export default function main(argv) { + let three = 1 + 2; + let message = "Hello, world"; + trace("Hello world, 1+2=" + three + "\n"); + trace(`1+2=${1+2}`); + if (argv.length) { + trace("argv[0]: " + argv[0] + "\n"); + } +} diff --git a/examples/cli/manifest.json b/examples/cli/manifest.json new file mode 100644 index 0000000000..3dd655cf26 --- /dev/null +++ b/examples/cli/manifest.json @@ -0,0 +1,8 @@ +{ + "include": "$(MODDABLE)/examples/manifest_base.json", + "modules": { + "*": [ + "./main" + ] + }, +} diff --git a/examples/data/text/main.js b/examples/data/text/main.js new file mode 100644 index 0000000000..ba7743dd86 --- /dev/null +++ b/examples/data/text/main.js @@ -0,0 +1,85 @@ +import { TextEncoder, TextDecoder } from "text"; + +const testCases = [ + { label: "empty", bytes: [], text: "" }, + { label: "euro", bytes: [226, 130, 172], text: "€" }, + { label: "CJK", bytes: [240, 160, 174, 183], text: "𠮷" }, + { + label: "sample paragraph", + text: "This is a sample paragraph.", + bytes: [ + 84, + 104, + 105, + 115, + 32, + 105, + 115, + 32, + 97, + 32, + 115, + 97, + 109, + 112, + 108, + 101, + 32, + 112, + 97, + 114, + 97, + 103, + 114, + 97, + 112, + 104, + 46, + ], + }, +]; + +function traceln(s) { + trace(s); + trace("\n"); +} + +function cmp(a, b) { + if (a.length !== b.length) return false; + for (let pos = 0; pos < a.length; pos++) { + if (a[pos] !== b[pos]) return false; + } + return true; +} + +function main() { + traceln("hello!"); + const enc = new TextEncoder(); + const dec = new TextDecoder(); + const data = enc.encode("blort!"); + traceln(data); + const text = dec.decode(data); + traceln(text); + + for (const { label, bytes, text } of testCases) { + const actual = enc.encode(text); + const actualString = dec.decode(Uint8Array.from(bytes)); + if (!cmp(actual, bytes)) { + traceln( + `FAIL: ${label}: expected ${JSON.stringify(bytes)} actual ${ + actual.length + } ${JSON.stringify(Array.from(actual))}` + ); + } else if (actualString !== text) { + traceln( + `FAIL: ${label}: expected ${JSON.stringify( + text + )} actual ${JSON.stringify(Array.from(actualString))}` + ); + } else { + traceln(`PASS: ${label}`); + } + } +} + +main(); diff --git a/examples/data/text/manifest.json b/examples/data/text/manifest.json new file mode 100644 index 0000000000..b34c51983e --- /dev/null +++ b/examples/data/text/manifest.json @@ -0,0 +1,13 @@ +{ + "include": [ + "$(MODDABLE)/examples/manifest_base.json", + ], + "modules": { + "*": [ + "./main", + "$(MODDABLE)/modules/data/text/text", + ], + }, + "preload": [ + ], +} diff --git a/modules/data/text/text.c b/modules/data/text/text.c new file mode 100644 index 0000000000..0af8401c20 --- /dev/null +++ b/modules/data/text/text.c @@ -0,0 +1,39 @@ +/** + * We take advantage of the internal representation of strings + * so that conversion is just copying bytes. + * + * "A string value is a pointer to a UTF-8 C string." + * -- https://github.com/Moddable-OpenSource/moddable/blob/public/documentation/xs/XS%20in%20C.md#strings + **/ +#include +#include "xs.h" + +/** + * Decode text from utf-8 to string + * + * @param {ArrayBuffer} xsArg(0) + * @returns {string} + */ +void xs_utf8_decode(xsMachine *the) +{ + char *data = xsToArrayBuffer(xsArg(0)); + size_t size = xsGetArrayBufferLength(xsArg(0)); + xsResult = xsStringBuffer(data, size + 1); + char *dest = xsToString(xsResult); + dest[size] = 0; +} + +/** + * Encode string of text as utf-8 bytes + * + * @param {string} xsArg(0) + * @returns {ArrayBuffer} + * + * WARNING: returned ArrayBuffer will be "detatched" in the 0-length case. + **/ +void xs_utf8_encode(xsMachine *the) +{ + xsStringValue string = xsToString(xsArg(0)); + int length = c_strlen(string); + xsResult = xsArrayBuffer(string, length); +} diff --git a/modules/data/text/text.js b/modules/data/text/text.js new file mode 100644 index 0000000000..01ddedaca8 --- /dev/null +++ b/modules/data/text/text.js @@ -0,0 +1,72 @@ +/** + * minimal TextDecoder + * No support for encodeInto. + * + * ref https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder + */ +export class TextEncoder { + constructor() { + + } + get encoding() { + return 'utf-8'; + } + encode(s) { + if (typeof s !== 'string') { + throw new TypeError(typeof s); + } + let arrayBuffer; + let bytes; + // fxArrayBuffer only allocates a chunk if length > 0 + // else new Uint8Array(enc.encode("")) + // throws new "detached buffer!" + if (s.length === 0) { + // arrayBuffer = undefined; + bytes = new Uint8Array(); + } else { + arrayBuffer = utf8_encode(s); + bytes = new Uint8Array(arrayBuffer); + } + // trace(`encode ${JSON.stringify(s)} -> ArrayBuffer(${arrayBuffer ? arrayBuffer.byteLength : ''}) -> Uint8Array(${bytes.length})\n`); + return bytes; + } +} + +const UTF8Names = ["unicode-1-1-utf-8", "utf-8", "utf8"]; + +/** + * minimal utf-8 TextDecoder + * no support for fatal, stream, etc. + * + * ref https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder + */ +export class TextDecoder { + /** + * @param {string=} utfLabel optional name for UTF-8 + * @param {*} options fatal is not supported + */ + constructor(utfLabel, options) { + if (utfLabel & !UTF8Names.includes(utfLabel)) { + throw new TypeError(utfLabel); + } + if (options && options.fatal) { + throw new TypeError('fatal not supported'); + } + } + /** + * @param {Uint8Array} bytes + * @param {*} options stream is not supported + */ + decode(bytes, options) { + if (options && options.stream) { + throw new TypeError('stream is unsupported'); + } + if (!(bytes instanceof Uint8Array)) { + throw new TypeError('arg must be Uint8Array'); + } + return utf8_decode(bytes.buffer); + } +} + +function utf8_encode(string) @ "xs_utf8_encode"; +function utf8_decode(buffer) @ "xs_utf8_decode"; diff --git a/modules/network/http/http.js b/modules/network/http/http.js index e6ad9f0c0d..e56344c532 100644 --- a/modules/network/http/http.js +++ b/modules/network/http/http.js @@ -549,6 +549,10 @@ function server(message, value, etc) { delete this.line; let request = this.callback(Server.headersComplete); // headers complete... let's see what to do with the request body + if ('upgrade' === request) { + return; + } + if (false === request) delete this.total; // ignore request body and just send response @@ -651,53 +655,54 @@ function server(message, value, etc) { let first; if (7 === this.state) { - let response = this.callback(Server.prepareResponse); // prepare response - - let parts = []; - let status = (!response || (undefined === response.status)) ? 200 : response.status; - let message = (!response || (undefined === response.reason)) ? reason(status) : response.reason.toString(); - parts.push("HTTP/1.1 ", status.toString(), " ", message, "\r\n", - "connection: ", "close\r\n"); - - if (response) { - let byteLength; - - for (let i = 0, headers = response.headers; headers && (i < headers.length); i += 2) { - parts.push(headers[i], ": ", headers[i + 1].toString(), "\r\n"); - if ("content-length" == headers[i].toLowerCase()) - byteLength = parseInt(headers[i + 1]); - } + let responseP = Promise.resolve(this.callback(Server.prepareResponse)); // prepare response + responseP.then(response => { + let parts = []; + let status = (!response || (undefined === response.status)) ? 200 : response.status; + let message = (!response || (undefined === response.reason)) ? reason(status) : response.reason.toString(); + parts.push("HTTP/1.1 ", status.toString(), " ", message, "\r\n", + "connection: ", "close\r\n"); + + if (response) { + let byteLength; + + for (let i = 0, headers = response.headers; headers && (i < headers.length); i += 2) { + parts.push(headers[i], ": ", headers[i + 1].toString(), "\r\n"); + if ("content-length" == headers[i].toLowerCase()) + byteLength = parseInt(headers[i + 1]); + } - this.body = response.body; - if (true === response.body) { - if (undefined === byteLength) { - this.flags = 2; - parts.push("transfer-encoding: chunked\r\n"); + this.body = response.body; + if (true === response.body) { + if (undefined === byteLength) { + this.flags = 2; + parts.push("transfer-encoding: chunked\r\n"); + } + else + this.flags = 4; + } + else { + this.flags = 1; + let count = 0; + if (this.body) + count = ("string" === typeof this.body) ? this.body.length : this.body.byteLength; //@@ utf-8 hell + parts.push("content-length: ", count.toString(), "\r\n"); } - else - this.flags = 4; - } - else { - this.flags = 1; - let count = 0; - if (this.body) - count = ("string" === typeof this.body) ? this.body.length : this.body.byteLength; //@@ utf-8 hell - parts.push("content-length: ", count.toString(), "\r\n"); } - } - else - parts.push("content-length: 0\r\n"); - parts.push("\r\n"); - socket.write.apply(socket, parts); + else + parts.push("content-length: 0\r\n"); + parts.push("\r\n"); + socket.write.apply(socket, parts); - this.state = 8; - first = true; + this.state = 8; + first = true; - if (this.body && (true !== this.body)) { - let count = ("string" === typeof this.body) ? this.body.length : this.body.byteLength; - if (count > (socket.write() - ((2 & this.flags) ? 8 : 0))) - return; - } + if (this.body && (true !== this.body)) { + let count = ("string" === typeof this.body) ? this.body.length : this.body.byteLength; + if (count > (socket.write() - ((2 & this.flags) ? 8 : 0))) + return; + } + }); } if (8 === this.state) { let body = this.body; diff --git a/modules/network/socket/lin/modSocket.c b/modules/network/socket/lin/modSocket.c index 133037b6d6..8e3bdc5f95 100644 --- a/modules/network/socket/lin/modSocket.c +++ b/modules/network/socket/lin/modSocket.c @@ -43,7 +43,7 @@ #define kRAW (2) #define kRxBufferSize 1024 -#define kTxBufferSize 1024 +#define kTxBufferSize 4096 typedef struct xsSocketRecord xsSocketRecord; typedef xsSocketRecord *xsSocket; diff --git a/tools/mcconfig.js b/tools/mcconfig.js index fd48597d94..820f9e5f0a 100644 --- a/tools/mcconfig.js +++ b/tools/mcconfig.js @@ -1078,7 +1078,7 @@ export default class extends Tool { var file = new XcodeFile(path, this); file.generate(this); } - else if (this.platform == "x-lin") { + else if (this.platform == "x-lin" || this.platform == "x-lin-cli" ) { this.dataPath = this.resourcesPath = this.tmpPath + this.slash + "resources"; this.createDirectory(this.resourcesPath); } @@ -1094,8 +1094,6 @@ export default class extends Tool { this.dataPath = this.resourcesPath = this.tmpPath + this.slash + "resources"; this.createDirectory(this.resourcesPath); } - else if (this.platform.startsWith("x-cli-")) { - } else { var folder = "mc", file; this.createDirectory(this.modulesPath + this.slash + folder); diff --git a/tools/mcconfig/make.x-cli-lin.mk b/tools/mcconfig/make.x-cli-lin.mk new file mode 100644 index 0000000000..8e5304a6be --- /dev/null +++ b/tools/mcconfig/make.x-cli-lin.mk @@ -0,0 +1,174 @@ +# +# Copyright (c) 2016-2017 Moddable Tech, Inc. +# +# This file is part of the Moddable SDK Tools. +# +# The Moddable SDK Tools is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# The Moddable SDK Tools is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with the Moddable SDK Tools. If not, see . +# + +PKGCONFIG = $(shell which pkg-config) +GLIB_COMPILE_RESOURCES = $(shell $(PKGCONFIG) --variable=glib_compile_resources gio-2.0) + +ifeq ($(DEBUG),1) + LIB_DIR = $(BUILD_DIR)/tmp/lin/debug/lib +else + ifeq ($(INSTRUMENT),1) + LIB_DIR = $(BUILD_DIR)/tmp/lin/instrument/lib + else + LIB_DIR = $(BUILD_DIR)/tmp/lin/release/lib + endif +endif + +XS_DIRECTORIES = \ + $(XS_DIR)/includes \ + $(XS_DIR)/platforms \ + $(XS_DIR)/sources + +XS_HEADERS = \ + $(XS_DIR)/platforms/lin_xs.h \ + $(XS_DIR)/platforms/xsPlatform.h \ + $(XS_DIR)/includes/xs.h \ + $(XS_DIR)/includes/xsmc.h \ + $(XS_DIR)/sources/xsCommon.h \ + $(XS_DIR)/sources/xsAll.h \ + $(XS_DIR)/sources/xsScript.h + +XS_OBJECTS = \ + $(LIB_DIR)/lin_xs.c.o \ + $(LIB_DIR)/xsAll.c.o \ + $(LIB_DIR)/xsAPI.c.o \ + $(LIB_DIR)/xsArguments.c.o \ + $(LIB_DIR)/xsArray.c.o \ + $(LIB_DIR)/xsAtomics.c.o \ + $(LIB_DIR)/xsBigInt.c.o \ + $(LIB_DIR)/xsBoolean.c.o \ + $(LIB_DIR)/xsCode.c.o \ + $(LIB_DIR)/xsCommon.c.o \ + $(LIB_DIR)/xsDataView.c.o \ + $(LIB_DIR)/xsDate.c.o \ + $(LIB_DIR)/xsDebug.c.o \ + $(LIB_DIR)/xsError.c.o \ + $(LIB_DIR)/xsFunction.c.o \ + $(LIB_DIR)/xsGenerator.c.o \ + $(LIB_DIR)/xsGlobal.c.o \ + $(LIB_DIR)/xsJSON.c.o \ + $(LIB_DIR)/xsLexical.c.o \ + $(LIB_DIR)/xsMapSet.c.o \ + $(LIB_DIR)/xsMarshall.c.o \ + $(LIB_DIR)/xsMath.c.o \ + $(LIB_DIR)/xsMemory.c.o \ + $(LIB_DIR)/xsModule.c.o \ + $(LIB_DIR)/xsNumber.c.o \ + $(LIB_DIR)/xsObject.c.o \ + $(LIB_DIR)/xsPlatforms.c.o \ + $(LIB_DIR)/xsProfile.c.o \ + $(LIB_DIR)/xsPromise.c.o \ + $(LIB_DIR)/xsProperty.c.o \ + $(LIB_DIR)/xsProxy.c.o \ + $(LIB_DIR)/xsRegExp.c.o \ + $(LIB_DIR)/xsRun.c.o \ + $(LIB_DIR)/xsScope.c.o \ + $(LIB_DIR)/xsScript.c.o \ + $(LIB_DIR)/xsSourceMap.c.o \ + $(LIB_DIR)/xsString.c.o \ + $(LIB_DIR)/xsSymbol.c.o \ + $(LIB_DIR)/xsSyntaxical.c.o \ + $(LIB_DIR)/xsTree.c.o \ + $(LIB_DIR)/xsType.c.o \ + $(LIB_DIR)/xsdtoa.c.o \ + $(LIB_DIR)/xsmc.c.o \ + $(LIB_DIR)/xsre.c.o + +HEADERS += $(XS_HEADERS) + +C_DEFINES = \ + -DXS_ARCHIVE=1 \ + -DINCLUDE_XSPLATFORM=1 \ + -DXSPLATFORM=\"lin_xs.h\" \ + -DmxRun=1 \ + -DmxParse=1 \ + -DmxNoFunctionLength=1 \ + -DmxNoFunctionName=1 \ + -DmxHostFunctionPrimitive=1 \ + -DmxFewGlobalsTable=1 +ifeq ($(INSTRUMENT),1) + C_DEFINES += -DMODINSTRUMENTATION=1 -DmxInstrument=1 +endif +C_INCLUDES += $(DIRECTORIES) +C_INCLUDES += $(foreach dir,$(XS_DIRECTORIES) $(TMP_DIR),-I$(dir)) + +XS_C_FLAGS = -fPIC -shared -c $(shell $(PKGCONFIG) --cflags gio-2.0) +ifeq ($(DEBUG),) + XS_C_FLAGS += -D_RELEASE=1 -O3 +else + XS_C_FLAGS += -D_DEBUG=1 -DmxDebug=1 -g -O0 -Wall -Wextra -Wno-missing-field-initializers -Wno-unused-parameter +# C_FLAGS += -DMC_MEMORY_DEBUG=1 +endif +C_FLAGS = $(XS_C_FLAGS) + +LINK_LIBRARIES = -lm -lc $(shell $(PKGCONFIG) --libs gio-2.0) + +# LINK_FLAGS = -arch i386 +LINK_FLAGS = -fPIC + +XSC = $(BUILD_DIR)/bin/lin/release/xsc +XSID = $(BUILD_DIR)/bin/lin/release/xsid +XSL = $(BUILD_DIR)/bin/lin/release/xsl +MCREZ = $(BUILD_DIR)/bin/lin/release/mcrez + +VPATH += $(XS_DIRECTORIES) + +.PHONY: all + +all: $(LIB_DIR) $(BIN_DIR)/$(NAME) + +$(LIB_DIR): + mkdir -p $(LIB_DIR) + +$(TMP_DIR)/lin_xs_cli.c: $(XS_DIR)/platforms/lin_xs_cli.c + cp $^ $@ + +$(TMP_DIR)/lin_xs_cli.o: $(TMP_DIR)/lin_xs_cli.c $(TMP_DIR)/mc.xs.h $(XS_HEADERS) + @echo "# copy" $(promiseJobsFlag = 1; GSource* idle_source = g_idle_source_new(); g_source_set_callback(idle_source, fxQueuePromiseJobsCallback, the, NULL); g_source_set_priority(idle_source, G_PRIORITY_DEFAULT); diff --git a/xs/platforms/lin_xs.h b/xs/platforms/lin_xs.h index 7e0dcefb1d..6ffb9f8ebe 100644 --- a/xs/platforms/lin_xs.h +++ b/xs/platforms/lin_xs.h @@ -88,6 +88,7 @@ extern void fxQueueWorkerJob(void* machine, void* job); void* waiterCondition; \ void* waiterData; \ void* waiterLink; \ + gboolean promiseJobsFlag; \ GMainContext* workerContext; \ GMutex workerMutex; \ txWorkerJob* workerQueue; diff --git a/xs/platforms/lin_xs_cli.c b/xs/platforms/lin_xs_cli.c new file mode 100644 index 0000000000..a92ec7c357 --- /dev/null +++ b/xs/platforms/lin_xs_cli.c @@ -0,0 +1,81 @@ +#include "lin_xs.h" + +#include "xsPlatform.h" +#include "xs.h" +#include "mc.xs.h" + +#include +#include +#include +#include +#include +#include +#define mxSeparator '/' + +#ifdef mxDebug + #define xsElseThrow(_ASSERTION) \ + ((void)((_ASSERTION) || (fxThrowMessage(the,(char *)__FILE__,__LINE__,XS_UNKNOWN_ERROR,"%s",strerror(errno)), 1))) +#else + #define xsElseThrow(_ASSERTION) \ + ((void)((_ASSERTION) || (fxThrowMessage(the,NULL,0,XS_UNKNOWN_ERROR,"%s",strerror(errno)), 1))) +#endif + +extern void fxRunPromiseJobs(void* machine); +extern txS1 fxPromiseIsPending(xsMachine* the, xsSlot* promise); +extern txS1 fxPromiseIsRejected(xsMachine* the, xsSlot* promise); + +void fxAbort(xsMachine* the, int status) +{ + exit(status); +} + +int main(int argc, char* argv[]) // here +{ + int error = 0; + + xsMachine* machine = fxPrepareMachine(NULL, xsPreparation(), "tool", NULL, NULL); + + xsBeginHost(machine); + { + xsVars(3); + { + xsTry { + int argi; + xsVar(0) = xsNewArray(0); + for (argi = 1; argi < argc; argi++) { + xsSetAt(xsVar(0), xsInteger(argi - 1), xsString(argv[argi])); + } + + fprintf(stderr, "lin_xs_cli: loading top-level main.js\n"); + xsVar(1) = xsAwaitImport("main", XS_IMPORT_DEFAULT); + fprintf(stderr, " lin_xs_cli: loaded\n"); + + fprintf(stderr, "lin_xs_cli: invoking main(argv)\n"); + xsVar(2) = xsCallFunction1(xsVar(1), xsUndefined, xsVar(0)); + fprintf(stderr, " lin_xs_cli: main(): entering event loop\n"); + + GMainContext *mainctx = g_main_context_default(); + while (the->promiseJobsFlag || fxPromiseIsPending(the, &xsVar(2))) { + while (the->promiseJobsFlag) { + the->promiseJobsFlag = 0; + fxRunPromiseJobs(the); + } + g_main_context_iteration(mainctx, TRUE); + } + if (fxPromiseIsRejected(the, &xsVar(2))) { + error = 1; + } + // ISSUE: g_main_context_unref(mainctx); causes xsDeleteMachine() below + // to hang in g_main_context_find_source_by_id() aquiring a lock. + } + xsCatch { + xsStringValue message = xsToString(xsException); + fprintf(stderr, "### %s\n", message); + error = 1; + } + } + } + xsEndHost(the); + xsDeleteMachine(machine); + return error; +} diff --git a/xs/sources/xsPlatforms.c b/xs/sources/xsPlatforms.c index 6b03ced872..1b69f25610 100644 --- a/xs/sources/xsPlatforms.c +++ b/xs/sources/xsPlatforms.c @@ -182,7 +182,11 @@ txID fxFindModule(txMachine* the, txSlot* realm, txID moduleID, txSlot* slot) relative = 1; } else if ((name[0] == '.') && (name[1] == '.') && (name[2] == '/')) { - dot = 2; + if ((name[3] == '.') && (name[4] == '.') && (name[5] == '/')) { + dot = 5; + } else { + dot = 2; + } relative = 1; } #if mxWindows @@ -225,11 +229,17 @@ txID fxFindModule(txMachine* the, txSlot* realm, txID moduleID, txSlot* slot) return XS_NO_ID; if (dot == 0) slash++; - else if (dot == 2) { + else if (dot > 1) { *slash = 0; slash = c_strrchr(path, mxSeparator); if (!slash) return XS_NO_ID; + if (dot > 2) { + *slash = 0; + slash = c_strrchr(path, mxSeparator); + if (!slash) + return XS_NO_ID; + } } if (preparation) { if (!c_strncmp(path, preparation->base, preparation->baseLength)) { diff --git a/xs/sources/xsPromise.c b/xs/sources/xsPromise.c index 8fc86916fb..9e3847d72f 100644 --- a/xs/sources/xsPromise.c +++ b/xs/sources/xsPromise.c @@ -1088,6 +1088,28 @@ void fxRunPromiseJobs(txMachine* the) } +txS1 fxPromiseIsPending(txMachine* the, txSlot* instance) { + if (instance->kind != XS_REFERENCE_KIND) { + return 0; + } + txSlot *promise = instance->value.reference; + if (!mxIsPromise(promise)) { + return 0; + } + txSlot* status = mxPromiseStatus(promise); + return status->value.integer == mxPendingStatus; +} +txS1 fxPromiseIsRejected(txMachine* the, txSlot* instance) { + if (instance->kind != XS_REFERENCE_KIND) { + return 0; + } + txSlot *promise = instance->value.reference; + if (!mxIsPromise(promise)) { + return 0; + } + txSlot* status = mxPromiseStatus(promise); + return status->value.integer == mxRejectedStatus; +}