摘要:最近在上做練習(xí),某道題的內(nèi)容是實(shí)現(xiàn)一個(gè)簡(jiǎn)稱語(yǔ)言解釋器等等均可。這篇文章準(zhǔn)備聊聊相關(guān)的一些知識(shí)和實(shí)現(xiàn)的細(xì)節(jié)。誕生于上世紀(jì)年代,曾運(yùn)用于早期的,想詳細(xì)了解的童鞋可以瀏覽維基百科。例如,當(dāng)某個(gè)存儲(chǔ)單元的值為時(shí),其執(zhí)行指令的結(jié)果為。
最近在 Codewars上做練習(xí),某道題的內(nèi)容是實(shí)現(xiàn)一個(gè) brainFuck(簡(jiǎn)稱BF語(yǔ)言) 解釋器(c/python/js等等均可)。動(dòng)手實(shí)踐的過(guò)程還是很有趣的,中間也遇到了各種各樣的問(wèn)題,最終通過(guò)測(cè)試,代碼也比較接近目前的 JS 高分 solution。這篇文章準(zhǔn)備聊聊相關(guān)的一些知識(shí)和實(shí)現(xiàn)的細(xì)節(jié)。
“腦洞大開(kāi)”的語(yǔ)言 —— BF 簡(jiǎn)介BrainFuck(后文以簡(jiǎn)寫B(tài)F指代),單是名字就很容易讓人腦洞大開(kāi),有種不可描述的“哲學(xué)”韻味。所以如果你忍不住 google 一下相關(guān)圖片的話,你會(huì)可能搜到類似下面的圖片:
畫面是不是已經(jīng)很生動(dòng)了?
BF 字面上的含義已經(jīng)暗示了這是一種不太直觀和容易閱讀的語(yǔ)言,當(dāng)然,在當(dāng)下也不會(huì)是一種通用語(yǔ)言。她屬于 Esolang(全稱 Esoteric programming language,直譯:深?yuàn)W的編程語(yǔ)言) 的范疇。
BF誕生于上世紀(jì)30年代,曾運(yùn)用于早期的 PC(Amiga),想詳細(xì)了解的童鞋可以瀏覽 維基百科。
BF 在當(dāng)下有什么應(yīng)用場(chǎng)景呢?
我想,對(duì)一個(gè)吃瓜群眾來(lái)說(shuō),了解了它,對(duì)寫作 逼格 和 腦力 的提升是很有用的。BF 具有極簡(jiǎn)主義(搞設(shè)計(jì)的童鞋的不妨了解下下)和功能齊全(圖靈完全)的特點(diǎn),旨在為用戶帶來(lái)困惑和挑戰(zhàn),豐富勞動(dòng)人民的業(yè)余生活。
8 種運(yùn)算符及其操作BF 作為一種極簡(jiǎn)的計(jì)算機(jī)語(yǔ)言,僅有8種運(yùn)算符,分別為: < > + - , . [ ],其功能對(duì)照如下表所示:
指令 | 含義 |
---|---|
< | 指針減一(指針左移) |
> | 指針加一(指針右移) |
+ | 指針指向的字節(jié)的值加一(當(dāng)前單元的數(shù)值+1) |
- | 指針指向的字節(jié)的值減一(當(dāng)前單元的數(shù)值-1) |
, | 輸入內(nèi)容到指針指向的單元(輸入一個(gè)字符,將其ASCII碼保存到當(dāng)前指針?biāo)竼卧?/td> |
. | 將指針指向的存儲(chǔ)單元的內(nèi)容作為字符輸出(將ASCII碼輸出為字符) |
[ | 如果指針指向的存儲(chǔ)單元為零,向后跳轉(zhuǎn)到對(duì)應(yīng)的 ] 指令處 |
] | 如果指針指向的存儲(chǔ)單元不為零,向前跳轉(zhuǎn)到對(duì)應(yīng)的 [ 指令處 |
BF基于一個(gè)簡(jiǎn)單的機(jī)器模型,除了八個(gè)指令,這個(gè)機(jī)器還包括:一個(gè)以字節(jié)為單位、被初始化為零的數(shù)組、一個(gè)指向該數(shù)組的指針(初始時(shí)指向數(shù)組的第一個(gè)字節(jié))、以及用于輸入輸出的兩個(gè)字節(jié)流。
對(duì) BF 比較有意思的比擬可以是這樣的:
如果把機(jī)器內(nèi)存看成是一個(gè)無(wú)限長(zhǎng)的“小火車”(類似于Array或List的數(shù)據(jù)結(jié)構(gòu)),每個(gè)車廂(存儲(chǔ)單元)里面的貨物默認(rèn)都是數(shù)字 0,列車上僅有一個(gè)列車員(數(shù)據(jù)指針);
<> 相當(dāng)于列車員在車廂間進(jìn)行移動(dòng),只有當(dāng)列車員在某節(jié)車廂時(shí),才能對(duì)車廂的貨物進(jìn)行操作;
+- 相當(dāng)于列車員對(duì)當(dāng)前所在車廂的貨物進(jìn)行增減;
, 相當(dāng)于列車在裝貨,列車員將當(dāng)前所在車廂的貨物替換為貨運(yùn)站輸入的單批次貨物(一個(gè)字符的ASCII碼);
. 會(huì)將當(dāng)前車廂里的貨物名稱(單個(gè)字符)輸出來(lái);
[] 相當(dāng)于列車員在滿足條件的兩節(jié)車廂間來(lái)回移動(dòng);
這里要注意的是,數(shù)組的每個(gè)單元都是一個(gè)字節(jié)大??;- 命令允許溢出,它可以用 255 個(gè) + 命令來(lái)代替。例如,當(dāng)某個(gè)存儲(chǔ)單元的值為 255 時(shí),其執(zhí)行指令 + 的結(jié)果為 0。類似的, 0 執(zhí)行指令 - 的結(jié)果為 255.
與通用語(yǔ)言的類比據(jù)此,BF的運(yùn)算符與通用語(yǔ)言的類比如下(以C語(yǔ)言為例):
BrainFuck | C |
---|---|
< | --ptr; |
> | ++ptr; |
+ | ++*ptr; |
- | --*ptr; |
, | *ptr = getchar(); |
. | putchar(*ptr); |
[ | while (*ptr) { |
] | } |
function brainLuck(code, input) { // @1 const inputChars = input.split(""); // @2 const codes = code.split(""); // @3 let codeIdx = 0; const arr = []; // @4 let arrIdx = 0; let outputStr = ""; // @5 while (codeIdx < code.length) { // @6 const ops = codes[codeIdx]; const handleLeftBracket = () => { // @7 if (~~arr[arrIdx] === 0) { let cnt = 1; while (cnt) { codeIdx++; if (codes[codeIdx] === "[") { cnt += 1; } if (codes[codeIdx] === "]") { cnt -= 1; } } } }; const handleRightBracket = () => { // @8 if (~~arr[arrIdx] !== 0) { let cnt = 1; while (cnt) { codeIdx--; if (codes[codeIdx] === "]") { cnt += 1; } if (codes[codeIdx] === "[") { cnt -= 1; } } } }; switch (ops) { // @9 case ">": arrIdx += 1; break; case "<": arrIdx -= 1; break; case "+": arr[arrIdx] = (~~arr[arrIdx] + 1) % 256; break; case "-": arr[arrIdx] = (~~arr[arrIdx] || 256) - 1; break; case ",": const iptChar = inputChars.shift(); arr[arrIdx] = iptChar ? iptChar.charCodeAt(0) : arr[arrIdx]; break; case ".": outputStr += String.fromCharCode(arr[arrIdx]); break; case "[": handleLeftBracket(); break; case "]": handleRightBracket(); break; } codeIdx++; // @10 } return outputStr; // @11 }實(shí)現(xiàn)思路闡述(與代碼中注釋的序號(hào)對(duì)應(yīng)):
(1) 我們實(shí)現(xiàn)了一個(gè)函數(shù) brainLuck 用以模擬 BF 語(yǔ)言的解釋執(zhí)行,函數(shù) brainLuck 的用例如下:
const code = ",+[-.,+]"; const input = "Parksben" + String.fromCharCode(255); const output = brainLuck(code, input); console.log(output); // -> "Parksben"
(2) 將輸入的字符串切割為單個(gè)字符,暫存進(jìn)數(shù)組 inputChars;
(3) 將 BF 程序切割為單個(gè)操作符,方便遍歷每個(gè)指令,用 codeIdx 作為下標(biāo)進(jìn)行遍歷;
(4) 聲明一個(gè)數(shù)組 arr 用以模擬機(jī)器內(nèi)存,過(guò)程產(chǎn)生的數(shù)值存儲(chǔ)到此數(shù)組中;
(5) 用字符串 outputStr 存儲(chǔ)程序的輸出;
(6) 遍歷 BF 運(yùn)算符,對(duì)不同指令進(jìn)行相應(yīng)的操作;
(7) 方法 handleLeftBracket,用以匹配到與當(dāng)前 [ 對(duì)應(yīng)的 ](通過(guò)操作下標(biāo) codeIdx);
(8) 方法 handleRightBracket,用以匹配到與當(dāng)前 ] 對(duì)應(yīng)的 [(通過(guò)操作下標(biāo) codeIdx);
(9) 用以處理不同指令的 switch 語(yǔ)句;
(10) codeIdx 加一,以向前遍歷 codes;
(11) 程序輸出;
延伸閱讀Brainfuck: a Programming Language or a Joke?
丹尼爾·克里斯托法尼的一些 BF 實(shí)例
深?yuàn)W的編程語(yǔ)言 - 維基百科
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/96890.html
摘要:還請(qǐng)同學(xué)跟我多多探討關(guān)于修改是異步還是同步的問(wèn)題先來(lái)看代碼上述代碼的結(jié)果完全就是同步的表現(xiàn),如果是異步的話,毫無(wú)疑問(wèn),第一個(gè)下的每個(gè)內(nèi)容都應(yīng)該是,第二個(gè)也應(yīng)該是。 回 @bf 同學(xué) 本篇文章不是筆記也不是心得,而是關(guān)于一個(gè)問(wèn)題的討論,問(wèn)題最初出現(xiàn)于https://segmentfault.com/q/1010000005630545?_ea=903562 由于 @bf 同學(xué)不方便...
閱讀 1251·2021-09-26 09:55
閱讀 3248·2019-08-30 15:55
閱讀 999·2019-08-30 15:53
閱讀 2315·2019-08-30 13:59
閱讀 2402·2019-08-29 13:08
閱讀 1127·2019-08-29 12:19
閱讀 3338·2019-08-26 13:41
閱讀 435·2019-08-26 13:24