Created
November 13, 2023 19:07
-
-
Save kddnewton/e2f50170649531c052481a013457b504 to your computer and use it in GitHub Desktop.
Compile Prism into YARV in JavaScript
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
import * as prism from "@ruby/prism"; | |
class RubyInteger { | |
constructor(value) { | |
this.value = value; | |
} | |
_plus(other) { | |
if (!(other instanceof RubyInteger)) { | |
throw new TypeError(`no implicit conversion of ${other.constructor.name} into Integer`); | |
} | |
return new RubyInteger(this.value + other.value); | |
} | |
} | |
class RubyMain { | |
puts(...args) { | |
console.log(...args); | |
} | |
} | |
const source = "puts 1 + 2"; | |
function slice(location) { | |
return source.slice(location.startOffset, location.startOffset + location.length); | |
} | |
function compile(node, used) { | |
switch (node.constructor.name) { | |
case "ArgumentsNode": { | |
for (const argument of node.arguments_) { | |
compile(argument, used); | |
} | |
break; | |
} | |
case "CallNode": { | |
if (node.receiver) { | |
compile(node.receiver, true); | |
} else { | |
insns.push({ type: "putself" }); | |
} | |
if (node.arguments_) { | |
compile(node.arguments_, true); | |
} | |
let mid = node.name; | |
switch (mid) { | |
case "+": | |
mid = "_plus"; | |
break; | |
} | |
insns.push({ | |
type: "send", | |
mid, | |
argc: (node.arguments_ || { arguments_: [] }).arguments_.length | |
}); | |
break; | |
} | |
case "IntegerNode": { | |
if (used) { | |
insns.push({ type: "putobject", value: new RubyInteger(parseInt(slice(node.location), 10)) }); | |
} | |
break; | |
} | |
case "ProgramNode": { | |
compile(node.statements, true); | |
break; | |
} | |
case "StatementsNode": { | |
const length = node.body.length; | |
for (let index = 0; index < length - 1; index++) { | |
compile(node.body[index], false); | |
} | |
compile(node.body[length - 1], used); | |
break; | |
} | |
default: { | |
console.error(`NOT SUPPORTED: ${node.constructor.name}`); | |
break; | |
} | |
} | |
} | |
function evaluate(insns) { | |
let sp = 0; | |
const maxSp = insns.length; | |
const stack = []; | |
while (sp < maxSp) { | |
switch (insns[sp].type) { | |
case "putobject": { | |
stack.push(insns[sp].value); | |
sp++; | |
break; | |
} | |
case "putself": { | |
stack.push(new RubyMain()); | |
sp++; | |
break; | |
} | |
case "send": { | |
let { argc, mid } = insns[sp]; | |
const args = stack.pop(argc); | |
const receiver = stack.pop(); | |
if (!receiver[mid]) { | |
throw new Error(`NoMethodError: ${mid}`); | |
} | |
stack.push(receiver[mid].call(receiver, args)); | |
sp++; | |
break; | |
} | |
} | |
} | |
} | |
const parse = await prism.loadPrism(); | |
const program = parse(source).value; | |
const insns = []; | |
compile(program, true); | |
evaluate(insns); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment