Last active
September 26, 2022 02:27
-
-
Save TwoSquirrels/1efea85af0a7661f85b467579790763d to your computer and use it in GitHub Desktop.
Brainf*ck Code Minifier (using Node.js)
This file contains hidden or 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
#! /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); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Features
+-><.,[]
以外の文字を取り除きます.,[]
で区切られたチャンク毎の処理を短く書き直します--help
,-h
は minify をする代わりにコマンドラインからの使い方を表示します--result-only
,-r
は結果のコードのみを標準出力に出力しますTODO
--no-optimize
,-n
は短く処理を書き直すような最適化をしません.,
を含まない階層の処理を短く書き直します><
を減らします