俺とbrainf*ck
MAFiG
ブログのネタが無くて自分が昔作ったリポジトリを漁っていたらこんなものやこんなものを見つけました。
約4年前、専門学校に入学してひたすらLinux Kernelを読んだりCを書いたりしていた頃ですね。
この頃にGoを初めて書いて、Better Cじゃん。となったのを覚えています。今でもGoは「良くも悪くもBetter C」という認識です。別にGoは嫌いじゃないです。積極的に書かないだけで。Not for meという感じ。炎上しそうなのでこれ以上言及はしません。
まぁそれはさておき、昔自分が書いたコードを読むのは羞恥心もデカイけど思い出に浸れるので楽しいです
MAFiG
作ったのは2019年の6月っぽい。これもまたサークルのLT駆動で作ったみたいですね。今もアドカレ駆動で記事を書いてる。
Make Any F*ck in Go
の名前通り、brainf*ck
の亜種を作るためのコードっぽいです。じゃあトークンをコードに埋め込むなよ。
トランスパイラも入ってる。
package main
import (
"fmt"
"io/ioutil"
"os"
)
// 各定義、ここ変えれば言語仕様変えられる
const (
INC byte = 43 // +
PLUS string = "(*ptr)++;"
DEC byte = 45 // -
MINS string = "(*ptr)--;"
NEX byte = 62 // >
MORE string = "ptr++;"
PRE byte = 60 // <
LESS string = "ptr--;"
RED byte = 44 // ,
COMA string = "*ptr = inpt();"
RIT byte = 46 // .
PROD string = "putchar(*ptr);"
JMP byte = 91 // [
LBRC string = "while (*ptr) {"
GOL byte = 93 // ]
RBRC string = "}"
)
func main() {
// 引数で必要なファイルが与えられなかった時にUsage
if len(os.Args) < 2 {
fmt.Println("Usage: " + os.Args[0] + " BFsourcefile")
return
}
// ソースファイルを読む
bfcode, err := ioutil.ReadFile(os.Args[1])
if err != nil {
fmt.Println("File read error")
return
}
// 出力(Cソース)ファイル
ccode, err := os.Create(os.Args[1] + ".c")
if err != nil {
fmt.Println("File create error")
return
}
// Cソースファイルの初期設定
err = cinit(ccode)
if err != nil {
fmt.Println("File init error")
return
}
ci := 0 // コードのインデックス
/*
コード解釈
各文字に対応するコードをCソースファイルに書き込む。
いちいちエラー処理もしてる
*/
for ci < len(bfcode) {
switch bfcode[ci] {
case INC:
n, err := ccode.WriteString(PLUS)
if err != nil || !(n > 0) {
fmt.Println("File write error!")
ccode.Close()
return
}
ci++
case DEC:
n, err := ccode.WriteString(MINS)
if err != nil || !(n > 0) {
fmt.Println("File write error!")
ccode.Close()
return
}
ci++
case NEX:
n, err := ccode.WriteString("\n" + MORE)
if err != nil || !(n > 0) {
fmt.Println("File write error!")
ccode.Close()
return
}
ci++
case PRE:
n, err := ccode.WriteString("\n" + LESS)
if err != nil || !(n > 0) {
fmt.Println("File write error!")
ccode.Close()
return
}
ci++
case RIT:
n, err := ccode.WriteString("\n" + PROD + "\n")
if err != nil || !(n > 0) {
fmt.Println("File write error!")
ccode.Close()
return
}
ci++
case RED:
n, err := ccode.WriteString("\n" + COMA + "\n")
if err != nil || !(n > 0) {
fmt.Println("File write error!")
ccode.Close()
return
}
ci++
case JMP:
n, err := ccode.WriteString("\n" + LBRC + "\n")
if err != nil || !(n > 0) {
fmt.Println("File write error!")
ccode.Close()
return
}
ci++
case GOL:
n, err := ccode.WriteString("\n" + RBRC + "\n")
if err != nil || !(n > 0) {
fmt.Println("File write error!")
ccode.Close()
return
}
ci++
default:
ci++
}
}
ccode.WriteString("putchar(13);putchar(10);\nreturn 0;\n}\n")
ccode.Close()
}
// Cのソースに共通部分を書き込む(各種関数、宣言等)
func cinit(dest *os.File) error {
_, err := dest.WriteString("#include <stdio.h>\n#include <stdlib.h>\n\nint inpt(void) {\nint c = getchar();\nif (c == EOF) {\nexit(0);\n}\nreturn c;\n}\n\nint main() {\nchar vmem[32767];\nchar *ptr = vmem;\n")
/*
#include <stdio.h>
#include <stdlib.h>
int inpt(void) {
int c = getchar();
if (c == EOF) {
exit(0);
}
return c;
}
void prnt(int c) {
putchar(c);
}
int main() {
char vmem[32767];
char *ptr = mem;
*/
if err != nil {
return err
}
return nil
}
インデントがグチャグチャで面白いけど、多分今の俺がGo書いてもこういうコードになる気がする。Goってそういう所がいいですよね。
fuck.go
の方はインタプリタですね。ひでぇファイル名
package main
import(
"fmt"
"io/ioutil"
"os"
)
const (
INC byte = 62 // +
DEC byte = 60 // -
NEX byte = 43 // >
PRE byte = 45 // <
RED byte = 44 // ,
RIT byte = 46 // .
JMP byte = 91 // [
GOL byte = 93 // ]
)
var BUFSIZE = 4096 // コード用のバッファサイズ
func main(){
if len(os.Args) < 2 {
fmt.Println("Usage: " + os.Args[0] + "file")
return
}
code, err := ioutil.ReadFile(os.Args[1])
if err != nil {
fmt.Println("File read error")
}
codebuf := make([]byte, BUFSIZE, BUFSIZE) // BUFSIZE分のスライスを確保
ptr := 0 // コードのポインタ
ci := 0 // コードのインデックス
buf := make([]byte, 1) // 入出力用のバッファ
for {
switch code[ci]{
case INC:
ptr++
case DEC:
ptr--
case NEX:
codebuf[ptr]++
case PRE:
codebuf[ptr]--
case RIT:
buf[0] = codebuf[ptr]
os.Stdout.Write(buf)
case RED:
os.Stdin.Read(buf)
codebuf[ptr] = buf[0]
case JMP:
if codebuf[ptr] == 0{
jc := 0 // jump counter
for {
ci ++
if code[ci] == JMP {
jc++
} else if code[ci] == GOL {
jc--
if jc < 0 {
break
}
}
}
}
case GOL:
if codebuf[ptr] != 0 {
jc := 0
for {
ci --
if code[ci] == GOL {
ci ++
} else if code[ci] == JMP {
jc --
if jc < 0 {
break
}
}
}
}
}
ci ++
if ci >= len(code) {
break
}
}
fmt.Printf("\n")
}
こっちは比較的きれい。ちゃんとコメントも書いてて偉いと思います。
llvm-f-ck-front
こちらは2019年の11月。
4年以上前だけど覚えています。LLVMのソースもドキュメントも読まずにClangの出力するIRだけでなんとか理解しようとしていた奴。
brainf*ck
のソースからLLVMのIRに変換するっぽいです。まだ動くかな
READMEを見ると粋な事を書いているので試してみる。
ちょっと修正(未定義の変数が使われてた。ゴミか?)したら動いた。
$ gcc src/main.c -o llvm-fuck-front
$ ./llvm-fuck-front README.md
$ cat README.ll
define void @main() #0 {
%1 = alloca i8*, align 8
%2 = call noalias i8* @calloc(i64 0, i64 114514) #3
store i8* %2, i8** %1, align 8
%3 = load i8*, i8** %1, align 8
%4 = getelementptr inbounds i8, i8* %3, i32 1
store i8* %4, i8** %1, align 8
%5 = load i8*, i8** %1, align 8
%6 = load i8, i8* %5, align 1
%7 = add i8 %6, 1
store i8 %7, i8* %5, align 1
%8 = load i8*, i8** %1, align 8
%9 = load i8, i8* %8, align 1
%10 = add i8 %9, 1
store i8 %10, i8* %8, align 1
%11 = load i8*, i8** %1, align 8
%12 = load i8, i8* %11, align 1
%13 = add i8 %12, 1
store i8 %13, i8* %11, align 1
%14 = load i8*, i8** %1, align 8
%15 = load i8, i8* %14, align 1
%16 = add i8 %15, 1
store i8 %16, i8* %14, align 1
%17 = load i8*, i8** %1, align 8
%18 = load i8, i8* %17, align 1
%19 = add i8 %18, 1
store i8 %19, i8* %17, align 1
%20 = load i8*, i8** %1, align 8
%21 = load i8, i8* %20, align 1
%22 = add i8 %21, 1
~~ snip ~~
%670 = call i32 @putchar(i32 %669)
ret void
}
declare noalias i8* @calloc(i64, i64) #1
declare i32 @getchar() #2
declare i32 @putchar(i32) #2
$ clang README.ll
warning: overriding the module target triple with x86_64-unknown-linux-gnu [-Woverride-module]
1 warning generated.
$ ls
a.out LICENSE llvm-fuck-front README.ll README.md src
$ ./a.out
Fucked Your Brain
おもしれーじゃん。
brainf*ck
にするためのREADMEが面白いので読んでみてください。
わざと全角にしたり、コードブロックの言語指定の所で無理やりコード書いたりしてる。
まとめ
なぜこの2つをピックアップしたかと言うと、brainf*ck
は言語処理系の勉強にぴったりだと言うことを伝えたいからです。
プログラミング始めたての専門学校一年生でもこれだけいろいろと遊べて飽きないのでおすすめです。
未だにBFで遊んでいます。明日はその記事を書こうかな。
おわりに
この記事はn01e0 Advent Calendar 2023の21日目の記事です。
ここまできたら明日もやりたいけど、SECCONの準備があるんだよな。
また、IPFactory OB Advent Calendar 2023の21日目の記事も兼ねています。
Comments