Created
August 31, 2023 09:31
-
-
Save hyrious/6caa045111772177d7c568b9dc64a595 to your computer and use it in GitHub Desktop.
backup
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width"> | |
<link rel="stylesheet" href="style.css"> | |
<script src="favicon.js"></script> | |
<script src="theme.js"></script> | |
<title>Ruby Marshal Visualization</title> | |
<style> | |
.title a { text-decoration: none } | |
main, section, article { margin: 0 5%; padding: 10px 0 } | |
p { margin: 10px 5% } | |
body.active { box-shadow: inset 0 0 0 5px } | |
.array::before { content: "["; color: grey } | |
.array { border: 1px solid var(--control); padding: 4px; display: inline-flex; flex-flow: row wrap; gap: 3px } | |
.fixnum { color: var(--link); } | |
.bignum { color: cyan } | |
.string { color: green } | |
.string:before, .string:after { content: '"' } | |
.symbol { color: pink } | |
.symbol:before { content: ":" } | |
.hash:before { content: "{" } | |
.hash:after { content: " }" } | |
.ivars:before { content: "#{" } | |
.ivars:after { content: "}" } | |
.symbol_ref { color: pink; border: 1px dashed; } | |
.object_ref { color: orange; border: 1px dashed; } | |
.content { border: 1px solid red } | |
.objects>* { border-bottom: 1px solid orange } | |
.symbols>* { border-bottom: 1px solid pink; } | |
.objects, .symbols { display: flex; flex-flow: column nowrap; } | |
</style> | |
</head> | |
<body> | |
<h1 class="title">Ruby Marshal Visualization<sup><a href="https://github.com/ruby/ruby/blob/master/doc/marshal.rdoc" target="_blank">[?]</a></sup></h1> | |
<p>Drop or <a id="upload" href="javascript: void 0">Upload</a> a Ruby marshal file here, e.g. Scripts.rvdata2</p> | |
<main id="output"></main> | |
<script src="dnu.js"></script> | |
<script> | |
dropAndUpload(document.body, upload, async function(file) { | |
output.textContent = '' | |
var buffer = await file.arrayBuffer() | |
var loader = new Loader(new DataView(buffer)) | |
while (loader.hasNext()) { | |
var content = document.createElement('div') | |
content.className = 'content' | |
output.append(content) | |
var obj = loader.get() | |
print(content, obj) | |
var objects = document.createElement('div') | |
objects.className = 'objects' | |
output.append(objects) | |
loader.objects.forEach(o => { | |
print(objects, o) | |
}) | |
var symbols = document.createElement('div') | |
symbols.className = 'symbols' | |
output.append(symbols) | |
loader.symbols.forEach(s => { | |
print(symbols, s) | |
}) | |
} | |
}) | |
function h(cls, ...args) { | |
var e = document.createElement('span') | |
e.className = cls | |
e.append(...args) | |
return e | |
} | |
var decoder = new TextDecoder('utf8', { fatal: true }) | |
var indexOf = Array.prototype.indexOf | |
function print(root, obj) { | |
if (obj.string) { | |
root.append(h('string', decode(obj.string))) | |
} else if (obj.symbol) { | |
root.append(h('symbol', decode(obj.symbol))) | |
} else if (obj.array) { | |
var el = h('array') | |
root.append(el) | |
obj.array.forEach(e => { print(el, e); el.append(', ') }) | |
} else if (obj.extends) { | |
var a = obj.extends // {obj, extends: []} | |
var el = h('extends') | |
root.append(el) | |
a.extends.forEach(e => { print(el, e); el.append(' e> ') }) | |
print(el, a.obj) | |
} else if (obj.object) { | |
var a = obj.object | |
var el = h('object') | |
root.append(el) | |
el.append('#<') | |
print(el, a.name) | |
a.ivars.forEach(([k, v]) => { | |
el.append(' ') | |
print(el, k) | |
el.append('=') | |
print(el, v) | |
}) | |
el.append('>') | |
} else if (obj.hash) { | |
var a = obj.hash | |
var el = h('hash') | |
root.append(el) | |
a.entries.forEach(([k, v]) => { | |
el.append(' ') | |
print(el, k) | |
el.append('=>') | |
print(el, v) | |
}) | |
if (a.default) { | |
el.append(' @default=') | |
print(el, a.default) | |
} | |
} else if (obj.ivars) { | |
var a = obj.ivars | |
var el = h('ivars') | |
root.append(el) | |
a.ivars.forEach(([k, v]) => { | |
print(el, k) | |
el.append('=') | |
print(el, v) | |
el.append(' ') | |
}) | |
print(el, a.obj) | |
} else { | |
// console.log(obj) | |
var k = Object.keys(obj)[0] | |
root.append(h(k, JSON.stringify(obj[k]))) | |
} | |
} | |
function decode(data) { | |
if (indexOf.call(data.subarray(0, 8000), 0) >= 0) return binary(data) | |
try { | |
return decoder.decode(data) | |
} catch { | |
return binary(data) | |
} | |
} | |
function binary(data) { | |
return `<${data.byteLength} bytes>` | |
} | |
class Loader { | |
constructor(view) { | |
this.pos = 0 | |
this.view = view | |
this.objects = null | |
this.symbols = null | |
} | |
hasNext() { | |
return this.pos + 2 < this.view.byteLength && this.view.getInt16(this.pos) === 0x408 | |
} | |
get() { | |
this.pos += 2 | |
this.objects = [] | |
this.symbols = [] | |
return this.#read_any() | |
} | |
get #buffer() { | |
return this.view.buffer | |
} | |
get #byteOffset() { | |
return this.view.byteOffset | |
} | |
#read_bytes(n) { | |
var ret = new Uint8Array(this.#buffer, this.#byteOffset + this.pos, n) | |
this.pos += n | |
return ret | |
} | |
#read_fixnum() { | |
var t = this.view.getInt8(this.pos++) | |
if (t == 0) return 0 | |
if (-4 <= t && t <= 4) { | |
var n = Math.abs(t), | |
shift = (4 - n) * 8, | |
bytes = this.#read_bytes(n), | |
a = 0 | |
for (var i = n - 1; i >= 0; --i) | |
a = a << 8 | bytes[i] | |
return t > 0 ? a : a << shift >> shift | |
} else | |
return t > 0 ? t - 5 : t + 5 | |
} | |
#read_chunk() { | |
return this.#read_bytes(this.#read_fixnum()) | |
} | |
#read_string() { | |
return { string: this.#read_chunk() } | |
} | |
#read_symbol() { | |
return { symbol: this.#read_chunk() } | |
} | |
#remember_object(o) { | |
return this.objects.push(o), o | |
} | |
#remember_symbol(s) { | |
return this.symbols.push(s), s | |
} | |
#read_entries(f) { | |
var entries = [], n = this.#read_fixnum() | |
while (n--) | |
if (f) f(this.#read_any(), this.#read_any()) | |
else entries.push([this.#read_any(), this.#read_any()]) | |
return f ? void 0 : entries | |
} | |
#read_bignum() { | |
var sign = this.view.getUint8(this.pos++), | |
n = this.#read_fixnum() * 2, | |
bytes = this.#read_bytes(n), | |
a = 0 | |
for (var i = 0; i < n; ++i) | |
a += bytes[i] * 2 ** (i * 8) | |
return { bignum: sign === 43 ? a : -a } | |
} | |
#read_float() { | |
var bytes = this.#read_chunk(), | |
s = new TextDecoder().decode(bytes) | |
return { float: s == 'inf' ? 1/0 : s == '-inf' ? -1/0 : s == 'nan' ? NaN : Number(s) } | |
} | |
#read_regexp() { | |
var bytes = this.#read_chunk(), | |
type = this.view.getUint8(this.pos++) | |
return { regexp: { source: bytes, type } } | |
} | |
#read_array(n, to) { | |
for (var i = 0; i < n; ++i) | |
to[i] = this.#read_any() | |
return to | |
} | |
#read_any() { | |
var c = this.view.getUint8(this.pos++) | |
// console.log(String.fromCharCode(c), c) | |
switch (c) { | |
case 84: return { true: true } | |
case 70: return { false: true } | |
case 48: return { nil: true } | |
case 105: return { fixnum: this.#read_fixnum() } | |
case 58: return this.#remember_symbol(this.#read_symbol()) | |
case 59: return { symbol_ref: this.#read_fixnum() } | |
case 64: return { object_ref: this.#read_fixnum() } | |
case 73: { | |
var obj = this.#remember_object(this.#read_any()) | |
var ivars = this.#read_entries() | |
return { ivars: { obj, ivars } } | |
} | |
case 101: { | |
var extends_ = [this.#read_any()] | |
while (this.view.getUint8(this.pos) === 101) | |
this.pos++, extends_.push(this.#read_any()) | |
var obj = this.#remember_object(this.#read_any()) | |
return { extends: { obj, extends: extends_ } } | |
} | |
case 91: { | |
var n = this.#read_fixnum(), | |
obj = this.#remember_object({ array: new Array(n) }) | |
this.#read_array(n, obj.array) | |
return obj | |
} | |
case 108: return this.#remember_object(this.#read_bignum()) | |
case 99: return this.#remember_object({ class: this.#read_chunk() }) | |
case 109: return this.#remember_object({ module: this.#read_chunk() }) | |
case 77: return this.#remember_object({ class_or_module: this.#read_chunk() }) | |
case 100: { | |
var name = this.#read_any() | |
var data = this.#read_any() | |
return this.#remember_object({ data: { name, data } }) | |
} | |
case 102: return this.#remember_object(this.#read_float()) | |
case 123: { | |
var obj = this.#remember_object({ hash: {} }) | |
obj.hash.entries = this.#read_entries() | |
return obj | |
} | |
case 125: { | |
var obj = this.#remember_object({ hash: {} }) | |
obj.hash.entries = this.#read_entries() | |
obj.hash.default = this.#read_any() | |
return obj | |
} | |
case 111: { | |
var obj = this.#remember_object({ object: { name: this.#read_any() } }) | |
obj.object.ivars = this.#read_entries() | |
return obj | |
} | |
case 47: return this.#remember_object(this.#read_regexp()) | |
case 34: return this.#remember_object(this.#read_string()) | |
case 83: { | |
var obj = this.#remember_object({ struct: { name: this.#read_any() } }) | |
obj.struct.members = this.#read_entries() | |
return obj | |
} | |
case 67: { | |
var obj = this.#remember_object({ wrapped: { name: this.#read_any() } }) | |
obj.wrapped.wrapped = this.#read_any() | |
return obj | |
} | |
case 117: { | |
var obj = this.#remember_object({ userDefined: { name: this.#read_any() } }) | |
obj.userDefined.userDefined = this.#read_chunk() | |
return obj | |
} | |
case 85: { | |
var obj = this.#remember_object({ userMarshal: { name: this.#read_any() } }) | |
obj.userMarshal.userMarshal = this.#read_any() | |
return obj | |
} | |
default: { | |
return { unknown: this.view.getUint8(this.pos - 1) } | |
} | |
} | |
} | |
} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment