Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions be-js/src/commonMain/kotlin/lang/temper/be/js/JsBackend.kt
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,7 @@ class JsBackend private constructor(
filePath("pair.js"),
filePath("regex.js"),
filePath("string.js"),
filePath("ws.js"),
)

override val specifics: NodeSpecifics get() = NodeSpecifics
Expand Down
11 changes: 11 additions & 0 deletions be-js/src/commonMain/kotlin/lang/temper/be/js/JsSupportNetwork.kt
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,8 @@ private val supportedAutoConnecteds = setOf(
// std/io
"stdSleep",
"stdReadLine",
"stdTermCols",
"stdTermRows",
// std/keyboard
"stdNextKeypress",
// std/net
Expand All @@ -476,6 +478,15 @@ private val supportedAutoConnecteds = setOf(
"NetResponse::getStatus",
"NetResponse::getContentType",
"NetResponse::getBodyContent",
// std/ws
"wsListen",
"wsAccept",
"wsConnect",
"wsSend",
"wsRecv",
"wsClose",
"WsServer",
"WsConnection",
)

private val supportedMappedConnecteds = mapOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ export * from "./net.js";
export * from "./pair.js";
export * from "./regex.js";
export * from "./string.js";
export * from "./ws.js";
16 changes: 16 additions & 0 deletions be-js/src/commonMain/resources/lang/temper/be/js/temper-core/io.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,22 @@ export function stdSleep(ms) {
/**
* @returns {Promise<string | null>}
*/
/** @returns {number} */
export function stdTermCols() {
if (typeof process !== 'undefined' && process.stdout && process.stdout.columns) {
return process.stdout.columns;
}
return 80;
}

/** @returns {number} */
export function stdTermRows() {
if (typeof process !== 'undefined' && process.stdout && process.stdout.rows) {
return process.stdout.rows;
}
return 24;
}

export function stdReadLine() {
return new Promise(resolve => {
if (typeof process !== 'undefined' && process.stdin) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@
"main": "index.js",
"type": "module",
"author": "https://github.com/mikesamuel",
"license": "Apache-2.0 OR MIT"
"license": "Apache-2.0 OR MIT",
"dependencies": {
"ws": "^8.18.0"
}
}
148 changes: 148 additions & 0 deletions be-js/src/commonMain/resources/lang/temper/be/js/temper-core/ws.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { empty } from "./core.js";

// We dynamically import 'ws' so this module works in environments where
// it is not installed (the functions will throw at call time instead of
// at import time).
let _WebSocketServer;
let _WebSocket;
const _wsReady = (async () => {
try {
const ws = await import("ws");
_WebSocketServer = ws.WebSocketServer;
_WebSocket = ws.default || ws.WebSocket;
} catch (_) {
// ws not available — functions will throw when called
}
})();

function _requireWs() {
if (!_WebSocket) {
throw new Error("WebSocket support requires the 'ws' npm package. Run: npm install ws");
}
}

function _setupMessageQueue(ws) {
if (!ws._mq) {
ws._mq = [];
ws._wr = [];
ws._closed = false;
ws.on("message", (data) => {
const msg = data.toString();
const waiting = ws._wr.shift();
if (waiting) { waiting(msg); }
else { ws._mq.push(msg); }
});
ws.on("close", () => {
ws._closed = true;
while (ws._wr.length) {
ws._wr.shift()(null);
}
});
ws.on("error", () => {
ws._closed = true;
while (ws._wr.length) {
ws._wr.shift()(null);
}
});
}
}

/**
* @param {number} port
* @returns {Promise<object>}
*/
export async function wsListen(port) {
await _wsReady;
_requireWs();
return new Promise((resolve, reject) => {
const server = new _WebSocketServer({ port });
server._pending = [];
server._waiting = [];
server.on("connection", (ws) => {
_setupMessageQueue(ws);
const waiting = server._waiting.shift();
if (waiting) { waiting(ws); }
else { server._pending.push(ws); }
});
server.on("listening", () => resolve(server));
server.on("error", reject);
});
}

/**
* @param {object} server
* @returns {Promise<object>}
*/
export function wsAccept(server) {
return new Promise((resolve) => {
const pending = server._pending.shift();
if (pending) { resolve(pending); }
else { server._waiting.push(resolve); }
});
}

/**
* @param {string} url
* @returns {Promise<object>}
*/
export async function wsConnect(url) {
await _wsReady;
_requireWs();
return new Promise((resolve, reject) => {
const ws = new _WebSocket(url);
_setupMessageQueue(ws);
ws.on("open", () => resolve(ws));
ws.on("error", reject);
});
}

/**
* @param {object} conn
* @param {string} msg
* @returns {Promise<Empty>}
*/
export function wsSend(conn, msg) {
return new Promise((resolve, reject) => {
try {
if (conn.readyState !== 1) { reject(new Error("not open")); return; }
conn.send(msg, (err) => {
if (err) reject(err);
else resolve(empty());
});
} catch (e) {
reject(e);
}
});
}

/**
* @param {object} conn
* @returns {Promise<string | null>}
*/
export function wsRecv(conn) {
_setupMessageQueue(conn);
return new Promise((resolve) => {
if (conn._closed && conn._mq.length === 0) {
resolve(null);
return;
}
const queued = conn._mq.shift();
if (queued !== undefined) { resolve(queued); }
else { conn._wr.push(resolve); }
});
}

/**
* @param {object} conn
* @returns {Promise<Empty>}
*/
export function wsClose(conn) {
return new Promise((resolve) => {
if (conn.readyState !== undefined && conn.readyState > 1) {
resolve(empty());
return;
}
conn.on("close", () => resolve(empty()));
conn.close();
});
}
21 changes: 21 additions & 0 deletions be-py/src/commonMain/kotlin/lang/temper/be/py/PySupportNetwork.kt
Original file line number Diff line number Diff line change
Expand Up @@ -911,6 +911,17 @@ val StdNetSend = PySeparateCode("std_net_send", RUNTIME)
val StdSleep = PySeparateCode("std_sleep", RUNTIME)
val StdReadLine = PySeparateCode("std_read_line", RUNTIME)
val StdNextKeypress = PySeparateCode("std_next_keypress", RUNTIME)
val StdTermCols = PySeparateCode("std_term_cols", RUNTIME)
val StdTermRows = PySeparateCode("std_term_rows", RUNTIME)

val WsServer = PyConnectedType("WsServer", RUNTIME)
val WsConnection = PyConnectedType("WsConnection", RUNTIME)
val StdWsListen = PySeparateCode("std_ws_listen", RUNTIME)
val StdWsAccept = PySeparateCode("std_ws_accept", RUNTIME)
val StdWsConnect = PySeparateCode("std_ws_connect", RUNTIME)
val StdWsSend = PySeparateCode("std_ws_send", RUNTIME)
val StdWsRecv = PySeparateCode("std_ws_recv", RUNTIME)
val StdWsClose = PySeparateCode("std_ws_close", RUNTIME)

val mathInf = PySeparateCode("inf", MATH)
val mathNan = PySeparateCode("nan", MATH)
Expand Down Expand Up @@ -1219,4 +1230,14 @@ private val pyConnections = mapOf(
"stdSleep" to StdSleep,
"stdReadLine" to StdReadLine,
"stdNextKeypress" to StdNextKeypress,
"stdTermCols" to StdTermCols,
"stdTermRows" to StdTermRows,
"wsListen" to StdWsListen,
"wsAccept" to StdWsAccept,
"wsConnect" to StdWsConnect,
"wsSend" to StdWsSend,
"wsRecv" to StdWsRecv,
"wsClose" to StdWsClose,
"WsServer" to WsServer,
"WsConnection" to WsConnection,
)
Loading