Skip to content

Instantly share code, notes, and snippets.

@TwoSquirrels
Last active September 26, 2022 02:27
Show Gist options
  • Save TwoSquirrels/1efea85af0a7661f85b467579790763d to your computer and use it in GitHub Desktop.
Save TwoSquirrels/1efea85af0a7661f85b467579790763d to your computer and use it in GitHub Desktop.
Brainf*ck Code Minifier (using Node.js)
#! /usr/bin/env node
// SPDX-License-Identifier: MIT
// (C) 2022 TwoSquirrels
// exports
module.exports = minifyBrainfuck;
// parse command line arguments
const [, thisFilePath, ...args] = process.argv;
if (require.main === module) {
const filePath = args.find((arg) => !arg.startsWith("-"));
const options = {
help: args.includes("--help") || args.includes("-h"),
resultOnly: args.includes("--result-only") || args.includes("-r"),
};
const path = require("path");
if (options.help) {
const jsPath = path.relative("", thisFilePath);
console.log(`
Usage: node ${jsPath} [options] <file>
${jsPath.match(/^(\.\.?\/)+/) ? jsPath : "./" + jsPath} [options] <file>
Options:
-h, --help Show this help message
-r, --result-only Only write to stdout the result
`);
process.exit(0);
}
if (!filePath) throw new Error("No file specified.");
const fs = require("fs");
// read file
const originalCode = fs.readFileSync(filePath, "utf8");
// minify code
const minifiedCode = minifyBrainfuck(originalCode);
// print minified code
if (options.resultOnly) {
process.stdout.write(minifiedCode);
} else {
console.log(`
Original file: ${path.resolve(filePath)}
Original size: ${Buffer.byteLength(originalCode)} bytes
Minified size: ${Buffer.byteLength(minifiedCode)} bytes
Minified code:
${minifiedCode}
`);
}
}
/**
* minify brainfuck code
* @param {string} originalCode - original brainfuck code
* @returns {string} minified brainfuck code
*/
function minifyBrainfuck(originalCode) {
let code = originalCode;
// remove non-brainfuck characters
code = code.replace(/[^+\-<>.,[\]]/gm, "");
// minify +-><
code = [...code]
.reduce((chunks, char) => {
if ("+-><".includes(char)) {
if (chunks.at(-1)?.type !== "simple")
chunks.push({ type: "simple", code: "" });
chunks.at(-1).code += char;
} else chunks.push({ type: "", code: char });
return chunks;
}, [])
.map((chunk) => {
if (chunk.type === "simple") return minifySimpleCode(chunk.code);
return chunk.code;
})
.join("");
return code;
}
function minifySimpleCode(originalCode) {
// check if code is simple
if (!originalCode.match(/^[+\-<>]+$/)) throw new Error("code is not simple.");
// simulate brainfuck
let pointer = 0;
let memoryStart = 0;
const memory = [0];
for (const char of originalCode) {
({
"+": () => memory[pointer - memoryStart]++,
"-": () => memory[pointer - memoryStart]--,
">": () => {
pointer++;
if (pointer - memoryStart >= memory.length) memory.push(0);
},
"<": () => {
pointer--;
if (pointer < memoryStart) {
memoryStart--;
memory.unshift(0);
}
},
}[char]());
}
// trim memory
while (memory.at(-1) === 0) memory.pop();
while (memory.at(0) === 0) {
memory.shift();
memoryStart++;
}
// generate minified code
const genCodeFillInOrder = (direction) => {
let code = "";
if (memory.length > 0) {
// move pointer to start
const start =
direction > 0 ? memoryStart : memoryStart + memory.length - 1;
code += start < 0 ? "<".repeat(-start) : ">".repeat(start);
// fill memory
(direction > 0 ? memory : memory.reverse()).forEach((value, index) => {
if (index > 0) code += direction > 0 ? ">" : "<";
if (value > 0) code += "+".repeat(value);
if (value < 0) code += "-".repeat(-value);
});
}
// move pointer to goal
const end =
memory.length === 0
? 0
: direction > 0
? memoryStart + memory.length - 1
: memoryStart;
code +=
pointer - end < 0 ? "<".repeat(end - pointer) : ">".repeat(pointer - end);
return code;
};
return ["to right", "to left"].reduce((minifiedCode, means) => {
let code = "";
switch (means) {
case "to right":
case "to left":
code = genCodeFillInOrder(means === "to right" ? 1 : -1);
break;
default:
throw new Error("unknown means.");
}
return code.length < minifiedCode.length ? code : minifiedCode;
}, originalCode);
}
@TwoSquirrels
Copy link
Author

TwoSquirrels commented Sep 25, 2022

Features

  • Brainf*ck の文字 +-><.,[] 以外の文字を取り除きます
  • .,[] で区切られたチャンク毎の処理を短く書き直します
  • コマンドラインから実行した際にファイルを指定すれば minify したコードを表示します
  • コマンドラインのオプション --help, -h は minify をする代わりにコマンドラインからの使い方を表示します
  • コマンドラインのオプション --result-only, -r は結果のコードのみを標準出力に出力します

TODO

  • コマンドラインのオプション --no-optimize, -n は短く処理を書き直すような最適化をしません
  • ファイルが与えられなかった場合は標準入力から受け取ります (パイプに便利です)
  • ., を含まない階層の処理を短く書き直します
  • 常に使用しない、または同時に使用することのないメモリを切り詰め、>< を減らします
  • NPM からインストールできます
  • Web から試せます

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment