Skip to content

Instantly share code, notes, and snippets.

@hyrious
Created August 30, 2023 04:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hyrious/db29db16bc11d8ca599f03b9966a95de to your computer and use it in GitHub Desktop.
Save hyrious/db29db16bc11d8ca599f03b9966a95de to your computer and use it in GitHub Desktop.
import os from "node:os";
import fs from "node:fs";
import path from "node:path";
import cp from "node:child_process";
/**
* ```js
* rb_eval(`p 1`) => '1'
* ```
*/
export async function rb_eval(code: string) {
const file = path.join(os.tmpdir(), `${Math.random().toString(36).slice(2)}.rb`);
await fs.promises.writeFile(file, code);
const output = await new Promise<string>((resolve, reject) =>
cp.exec(`ruby ${JSON.stringify(file)}`, (err, stdout, stderr) => {
err ? reject(err) : stderr ? reject(new Error(stderr)) : resolve(stdout);
})
);
await fs.promises.unlink(file);
return output;
}
/**
* ```js
* rb_dump(`nil`) => Uint8Array { 4, 8, 0x30 }
* ```
*/
export async function rb_dump(code: string): Promise<Uint8Array> {
const hex = await rb_eval(`s = Marshal.dump begin ${code} end; print s.unpack1 'H*'`);
return Buffer.from(hex, "hex");
}
import http, { IncomingMessage, ServerResponse } from "node:http";
http
.createServer((req, res) => {
res.setHeader("Access-Control-Allow-Origin", "*");
if (req.method === "GET") {
if (req.url === "/") return writeIndexHTML(res);
else {
res.statusCode = 404;
return res.end();
}
}
if (req.method === "POST") {
if (req.url === "/rb_dump") {
return text(req)
.then(rb_dump)
.then(data => {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(data)); // {"type":"Buffer","data":[1]}
})
.catch(err => {
res.writeHead(400, { "Content-Type": "text/plain" });
res.end(err.message);
});
}
}
})
.listen(3000, () => {
console.log("serving http://localhost:3000");
});
async function text(req: IncomingMessage): Promise<string> {
const chunks: Uint8Array[] = [];
for await (const chunk of req) chunks.push(chunk);
return Buffer.concat(chunks).toString("utf-8");
}
function writeIndexHTML(res: ServerResponse) {
res.writeHead(200, { "Content-Type": "text/html" });
res.end(indexHTML);
}
const indexHTML = `<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>Test</title></head><body><style>
html { height: 100%; box-sizing: border-box }
*, *:before, *:after { box-sizing: inherit }
body { height: 100%; margin: 0; padding: 8px }
body { display: flex; flex-direction: column; gap: 8px }
textarea { flex: 1; tab-size: 2; font-size: 16px; padding: 8px; line-height: 1.2; white-space: pre }
button { padding: 8px 16px; font-size: 16px }
.row { display: flex; gap: 8px } .row>* { flex: 1 }
input { font-size: 16px; padding: 8px; font-family: monospace }
</style>
<textarea id="ruby" placeholder="nil">nil</textarea><button id="dump">DUMP</button>
<textarea id="output" placeholder="04 08 30"></textarea>
<textarea id="output2" placeholder="04 08 T_NIL"></textarea>
<div class="row"><input id="char" placeholder="a"><input id="char_code" placeholder="97"></div>
<script type="module">
var IDX=256, HEX=[]
while (IDX--) HEX[IDX] = (IDX + 256).toString(16).slice(1)
var CODES = {45:"B_NEGATIVE",43:"B_POSITIVE",1:"RE_IGNORECASE",4:"RE_MULTILINE",91:"T_ARRAY",108:"T_BIGNUM",99:"T_CLASS",100:"T_DATA",101:"T_EXTENDED",70:"T_FALSE",105:"T_FIXNUM",102:"T_FLOAT",123:"T_HASH",125:"T_HASH_DEF",73:"T_IVAR",64:"T_LINK",109:"T_MODULE",77:"T_MODULE_OLD",48:"T_NIL",111:"T_OBJECT",47:"T_REGEXP",34:"T_STRING",83:"T_STRUCT",58:"T_SYMBOL",59:"T_SYMLINK",84:"T_TRUE",67:"T_UCLASS",117:"T_USERDEF",85:"T_USERMARSHAL"}
dump.onclick = async () => {
sessionStorage.setItem('ruby', ruby.value)
output.disabled = output2.disabled = true
const res = await fetch('/rb_dump', { method: 'POST', body: ruby.value })
if (!res.ok) {
output.value = await res.text()
output2.value = ''
output.disabled = output2.disabled = false
return
}
const {data} = await res.json()
let formatted = '', formatted2 = ''
for (let i = 0; i < data.length; ++i) {
formatted += HEX[data[i]] + ' '
formatted2 += ((i > 1 ? CODES[data[i]] : '') || HEX[data[i]]) + ' '
if (i % 16 === 15) formatted += '\\n'
}
output.value = formatted
output2.value = formatted2
output.disabled = output2.disabled = false
}
if (sessionStorage.getItem('ruby')) {
ruby.value = sessionStorage.getItem('ruby')
}
char.oninput = () => {
char_code.value = char.value.charCodeAt(0)
}
char_code.oninput = () => {
char.value = String.fromCharCode(char_code.value)
}
</script></body></html>
`;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment