
mokuji目次
ブレインとして
世の中には頭を掻き乱してくるとんでもない言語があります
8命令しかないbrain fuc⚪︎kです
Brainfuckプログラムは、以下の8個の実行可能な命令から成る(他の文字は無視され、読み飛ばされる)。
>
ポインタをインクリメントする。ポインタをptrとすると、C言語の「ptr++;
」に相当する。<
ポインタをデクリメントする。C言語の「ptr--;
」に相当。+
ポインタが指す値をインクリメントする。C言語の「(*ptr)++;
」に相当。-
ポインタが指す値をデクリメントする。C言語の「(*ptr)--;
」に相当。.
ポインタが指す値を出力に書き出す。C言語の「putchar(*ptr);
」に相当。,
入力から1バイト読み込んで、ポインタが指す先に代入する。C言語の「*ptr=getchar();
」に相当。[
ポインタが指す値が0なら、対応する]
の直後にジャンプする。C言語の「while(*ptr){
」に相当。]
ポインタが指す値が0でないなら、対応する[
(の直後[注釈 1])にジャンプする。C言語の「}
」に相当[注釈 2]。
https://ja.m.wikipedia.org/wiki/Brainfuck
何番煎じかわかんないですが、これの僕verを作りました
#include <stdio.h>#include <locale.h>#include <string.h>#include <stdlib.h>#ifndef NODO_DEBUG#define NODO_DEBUG 0#endif#ifndef NODO_CODE_ALLOCATE_SIZE#define NODO_CODE_ALLOCATE_SIZE 100000#endifenum { LOOP_START, LOOP_END, PLUS, MINUS, SHIFT_RIGHT, SHIFT_LEFT, INPUT, PRINT, END} typedef CODE;void brain(CODE *i,int P,int debug){unsigned char s[P];for(int cp=0,p=0,d[P],dp=0,f=0;i[cp]!=END;cp++){f?(i[cp]==LOOP_START?f++:i[cp]==LOOP_END?f--:!0):i[cp]==PLUS?(s[p]>254?s[p]=0:++s[p]):i[cp]==MINUS?(s[p]<1?s[p]=255:--s[p]):i[cp]==SHIFT_LEFT?--p:i[cp]==SHIFT_RIGHT?++p:i[cp]==INPUT?s[p]=getchar():i[cp]==PRINT?putchar(s[p]):i[cp]==LOOP_START?(s[p]?d[dp++]=cp:(f=1)):i[cp]==LOOP_END?(s[p]?cp=d[dp-1]:dp--):perror("ERROR OP");debug?printf("%d %d %d\n",i[cp], cp, s[p]):(0);}}int main(int argc, char **argv){ // 初期化 setlocale(LC_ALL, ""); char *s = (char*)malloc(sizeof(char)*NODO_CODE_ALLOCATE_SIZE*10); int i=1; CODE code[NODO_CODE_ALLOCATE_SIZE]; printf("の: '['\nど: ']'\nか: '+'\nは: '-'\nのどかは: '>'\nのん: '<'\nちゃん: ','\nのんちゃん: '.'\n"); if (argc < 2){ scanf("%[^EOF]c", s); for(char *p = strtok(s, "!");p != NULL;(p = strtok(NULL, "!"))) { if(!strcmp("の", p)){ code[i++] = LOOP_START; } else if (!strcmp("ど", p)){ code[i++] = LOOP_END; } else if (!strcmp("か", p)){ code[i++] = PLUS; } else if (!strcmp("は", p)){ code[i++] = MINUS; } else if (!strcmp("のどかは", p)){ code[i++] = SHIFT_RIGHT; } else if (!strcmp("のん", p)){ code[i++] = SHIFT_LEFT; } else if (!strcmp("ちゃん", p)){ code[i++] = INPUT; } else if (!strcmp("のんちゃん", p)){ code[i++] = PRINT; } } code[i] = END; } else { if (!strcmp(argv[1], "-bc")){ scanf("%[^EOF]c", s); for(int i=0;;i++){ if((CODE)(s[i]-'0')==END){ code[i+1] = END; break; } NODO_DEBUG ? printf("%c %d\n", s[i], (CODE)(s[i]-'0')) : 0; code[i+1] = (CODE)(s[i]-'0'); } } else if (!strcmp(argv[1], "-ts")){ scanf("%[^EOF]c", s); for(int i=0;s[i]!=NULL;printf("%s!",s[i]=='['?"の":s[i]==']'?"ど":s[i]=='+'?"か":s[i]=='-'?"は":s[i]=='>'?"のどかは":s[i]=='<'?"のん":s[i]==','?"ちゃん":s[i]=='.'?"のんちゃん":""),i++); } } for(int n=0;code[n]!=END&&NODO_DEBUG!=0;n++,printf("%d",code[n])); brain(code+1,NODO_CODE_ALLOCATE_SIZE,NODO_DEBUG); return 0;}
トランスパイルすることと、バイトコード入力、インタプリタ動作をサポートしてます
日本語の部分を変えれば⚪︎⚪︎言語が作れますね
簡単な解説
これだけのコードでも現代でよく用いられる言語処理系の基本を全て押さえています
enum { LOOP_START, LOOP_END, PLUS, MINUS, SHIFT_RIGHT, SHIFT_LEFT, INPUT, PRINT, END} typedef CODE;
バイトコード命令が列挙されています
8命令しかないので簡潔です
void brain(CODE *i,int P,int debug){unsigned char s[P];for(int cp=0,p=0,d[P],dp=0,f=0;i[cp]!=END;cp++){f?(i[cp]==LOOP_START?f++:i[cp]==LOOP_END?f--:!0):i[cp]==PLUS?(s[p]>254?s[p]=0:++s[p]):i[cp]==MINUS?(s[p]<1?s[p]=255:--s[p]):i[cp]==SHIFT_LEFT?--p:i[cp]==SHIFT_RIGHT?++p:i[cp]==INPUT?s[p]=getchar():i[cp]==PRINT?putchar(s[p]):i[cp]==LOOP_START?(s[p]?d[dp++]=cp:(f=1)):i[cp]==LOOP_END?(s[p]?cp=d[dp-1]:dp--):perror("ERROR OP");debug?printf("%d %d %d\n",i[cp], cp, s[p]):(0);}}
eval(評価機)が定義されています
受け取った命令列を解釈して、内部のレジスタ
- コードポインタcp
- レジスタのポインタp
- レジスタ列となるs配列
- ループ文のためにジャンプ記録レジスタ列のd配列
- そのポインタ(ジャンプする度に場所を記憶する深さ用ポインタ)のdp
- 多重ループ用のループネストフラグf
に値を移動、追加しています
単純なレジスタマシンですね
int main(int argc, char **argv){ // 初期化 setlocale(LC_ALL, ""); char *s = (char*)malloc(sizeof(char)*NODO_CODE_ALLOCATE_SIZE*10); int i=1; CODE code[NODO_CODE_ALLOCATE_SIZE]; printf("の: '['\nど: ']'\nか: '+'\nは: '-'\nのどかは: '>'\nのん: '<'\nちゃん: ','\nのんちゃん: '.'\n"); if (argc < 2){ scanf("%[^EOF]c", s); for(char *p = strtok(s, "!");p != NULL;(p = strtok(NULL, "!"))) { if(!strcmp("の", p)){ code[i++] = LOOP_START; } else if (!strcmp("ど", p)){ code[i++] = LOOP_END; } else if (!strcmp("か", p)){ code[i++] = PLUS; } else if (!strcmp("は", p)){ code[i++] = MINUS; } else if (!strcmp("のどかは", p)){ code[i++] = SHIFT_RIGHT; } else if (!strcmp("のん", p)){ code[i++] = SHIFT_LEFT; } else if (!strcmp("ちゃん", p)){ code[i++] = INPUT; } else if (!strcmp("のんちゃん", p)){ code[i++] = PRINT; } } code[i] = END; } else { if (!strcmp(argv[1], "-bc")){ scanf("%[^EOF]c", s); for(int i=0;;i++){ if((CODE)(s[i]-'0')==END){ code[i+1] = END; break; } NODO_DEBUG ? printf("%c %d\n", s[i], (CODE)(s[i]-'0')) : 0; code[i+1] = (CODE)(s[i]-'0'); } } else if (!strcmp(argv[1], "-ts")){ scanf("%[^EOF]c", s); for(int i=0;s[i]!=NULL;printf("%s!",s[i]=='['?"の":s[i]==']'?"ど":s[i]=='+'?"か":s[i]=='-'?"は":s[i]=='>'?"のどかは":s[i]=='<'?"のん":s[i]==','?"ちゃん":s[i]=='.'?"のんちゃん":""),i++); } } for(int n=0;code[n]!=END&&NODO_DEBUG!=0;n++,printf("%d",code[n])); brain(code+1,NODO_CODE_ALLOCATE_SIZE,NODO_DEBUG); return 0;}
最後は怒涛の勢いで解説します(面倒になったので)
みんなよくみるmain関数は日本語のためにロケール対応、文字を受け取る配列の初期化
真ん中で簡易的なトークナイザとパーサーが動いています
トークナイザは!区切りするようにC言語に存在する便利関数を用いてループ最中に一致した日本語を一番最初の定義に対応させてるだけです(トークンと命令一対一対応)
そしてこの部分はプログラムに渡された引数が“-bc”だったら
for(int i=0;;i++){ if((CODE)(s[i]-'0')==END){ code[i+1] = END; break; } NODO_DEBUG ? printf("%c %d\n", s[i], (CODE)(s[i]-'0')) : 0; code[i+1] = (CODE)(s[i]-'0'); }
によって文字を数字にした後にバイトコードとして格納
”-ts“ならば
for(int i=0;s[i]!=NULL;printf("%s!",s[i]=='['?"の":s[i]==']'?"ど":s[i]=='+'?"か":s[i]=='-'?"は":s[i]=='>'?"のどかは":s[i]=='<'?"のん":s[i]==','?"ちゃん":s[i]=='.'?"のんちゃん":""),i++);
によってトランスパイル(一対一対応なので置き換えするだけ)
しています
以上です
お疲れ様でした