Skip to content

Instantly share code, notes, and snippets.

@kddnewton
Created November 13, 2023 19:07
Show Gist options
  • Save kddnewton/e2f50170649531c052481a013457b504 to your computer and use it in GitHub Desktop.
Save kddnewton/e2f50170649531c052481a013457b504 to your computer and use it in GitHub Desktop.
Compile Prism into YARV in JavaScript
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