A quine for the MBLF programming language
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

227 lines
6.1 KiB

#!/usr/bin/env node
console.time("Compiled in");
const fs = require("fs");
const args = process.argv.slice(2, process.argv.length);
if (!args[0] && args[0] !== 0) {
console.error("Usage: mblf <input file> <output file>");
process.exit(1);
} else if (!args[1] && args[1] !== 0) {
console.error("Error: Please provide an output path.");
process.exit(1);
}
function parseNum(number) {
if (typeof number == "string") {
if (number.startsWith("0x")) {
return parseInt(number.slice(2, number.length), 16);
} else if (number.startsWith("\"") && number.endsWith("\"")) {
return number.charCodeAt(1);
} else {
return parseInt(number);
}
} else {
return number;
}
}
let bfOutput = [];
let variables = [];
let ptr = 0;
let prevPtr = 0;
function instruct(instruction, arg) {
let pointedVariable;
let number;
switch (instruction) {
case "var":
if (variables.indexOf(arg) != -1) {
return `${arg} already exists.`;
}
let emptyAddress = 0;
while (!(variables[emptyAddress] === undefined)) {
emptyAddress++;
}
variables[emptyAddress] = arg;
break;
case "delvar":
delete variables[variables.indexOf(arg)];
break;
case "point":
if (bfOutput[bfOutput.length-1] !== undefined &&
(bfOutput[bfOutput.length-1].endsWith("<") ||
bfOutput[bfOutput.length-1].endsWith(">"))) {
bfOutput.pop();
ptr = prevPtr;
}
let variable = variables.indexOf(arg);
if (variable == -1) {
return `${arg} is not a variable.`;
}
let distance = variable - ptr;
prevPtr = ptr;
if (distance > 0) {
bfOutput.push(">".repeat(distance));
} else if (distance < 0) {
bfOutput.push("<".repeat(-distance));
}
ptr = variable;
break;
case "pointm":
bfOutput.push("<+[-<+]-"); // thank you mixtela
ptr = variables.indexOf(arg);
break;
case "add":
number = parseNum(arg);
if (Number.isNaN(number)) {
return "Failed to parse number.";
}
bfOutput.push("+".repeat(number));
break;
case "addb":
number = parseNum(arg);
if (Number.isNaN(number)) {
return "Failed to parse number.";
}
pointedVariable = variables[ptr];
instruct("var", "__temp");
instruct("point", "__temp");
instruct("add", Math.floor(number / 16));
bfOutput.push("[");
instruct("sub", "1");
instruct("point", pointedVariable);
instruct("add", "16");
instruct("point", "__temp");
bfOutput.push("]");
instruct("point", pointedVariable);
instruct("add", number - Math.floor(number / 16) * 16);
instruct("delvar", "__temp");
break;
case "addv":
pointedVariable = variables[ptr];
bfOutput.push("[");
instruct("sub", "1");
instruct("point", arg);
instruct("add", "1");
instruct("point", pointedVariable);
bfOutput.push("]");
break;
case "sub":
number = parseNum(arg);
if (Number.isNaN(number)) {
return "Failed to parse number.";
}
bfOutput.push("-".repeat(number));
break;
case "subb":
number = parseNum(arg);
if (Number.isNaN(number)) {
return "Failed to parse number.";
}
pointedVariable = variables[ptr];
instruct("var", "__temp");
instruct("point", "__temp");
instruct("add", Math.floor(number / 10));
bfOutput.push("[");
instruct("sub", "1");
instruct("point", pointedVariable);
instruct("sub", "10");
instruct("point", "__temp");
bfOutput.push("]");
instruct("point", pointedVariable);
instruct("sub", number - Math.floor(number / 10) * 10);
instruct("delvar", "__temp");
break;
case "subv":
pointedVariable = variables[ptr];
bfOutput.push("[");
instruct("sub", "1");
instruct("point", arg);
instruct("sub", "1");
instruct("point", pointedVariable);
bfOutput.push("]");
break;
case "copy":
pointedVariable = variables[ptr];
instruct("var", "__temp");
bfOutput.push("[");
instruct("sub", "1");
instruct("point", arg);
instruct("add", "1");
instruct("point", "__temp");
instruct("add", "1");
instruct("point", pointedVariable);
bfOutput.push("]");
instruct("point", "__temp");
bfOutput.push("[");
instruct("sub", "1");
instruct("point", pointedVariable);
instruct("add", "1");
instruct("point", "__temp");
bfOutput.push("]");
instruct("delvar", "__temp");
break;
case "setz":
bfOutput.push("[-]");
break;
case "getchr":
bfOutput.push(",");
break;
case "print":
bfOutput.push(".");
break;
case "[":
bfOutput.push("[");
break;
case "]":
bfOutput.push("]");
break;
default:
if (instruction !== "") {
return `${instruction} is not an instruction.`
}
break;
}
}
function runMblf(inputFile) {
let file;
try {
file = fs.readFileSync(inputFile, "utf8")
} catch (e) {
console.error(`Error: ${e.message}`);
process.exit(1);
}
return file
.replace(/\r/gm, "")
.split("\n")
.forEach((item, line) => {
let statement = item.split(";;")[0].trim().split(" ");
if (statement[0] == "#include") {
try {
runMblf(item.split("\"")[1]);
} catch (e) {
if (e.name == "RangeError" && e.message == "Maximum call stack size exceeded") {
console.error(`[${inputFile}] Error in line ${line+1}: Call stack size exceeded. Check for cyclic dependencies.`);
process.exit(1);
} else {
throw e;
}
}
} else {
let instructAttempt = instruct(statement[0], statement.slice(1, statement.length).join(" "));
if (instructAttempt !== undefined) {
console.error(`[${inputFile}] Error in line ${line+1}: ${instructAttempt}`);
process.exit(1);
}
}
});
}
runMblf(args[0]);
fs.writeFileSync(args[1], bfOutput.join(""));
console.timeEnd("Compiled in");