MAFiG

ブログのネタが無くて自分が昔作ったリポジトリを漁っていたらこんなものこんなものを見つけました。

約4年前、専門学校に入学してひたすらLinux Kernelを読んだりCを書いたりしていた頃ですね。

この頃にGoを初めて書いて、Better Cじゃん。となったのを覚えています。今でもGoは「良くも悪くもBetter C」という認識です。別にGoは嫌いじゃないです。積極的に書かないだけで。Not for meという感じ。炎上しそうなのでこれ以上言及はしません。

まぁそれはさておき、昔自分が書いたコードを読むのは羞恥心もデカイけど思い出に浸れるので楽しいです

MAFiG

repo

作ったのは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

repo

こちらは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が面白いので読んでみてください。

README.md

わざと全角にしたり、コードブロックの言語指定の所で無理やりコード書いたりしてる。

まとめ

なぜこの2つをピックアップしたかと言うと、brainf*ckは言語処理系の勉強にぴったりだと言うことを伝えたいからです。

プログラミング始めたての専門学校一年生でもこれだけいろいろと遊べて飽きないのでおすすめです。

未だにBFで遊んでいます。明日はその記事を書こうかな。

おわりに

この記事はn01e0 Advent Calendar 2023の21日目の記事です。

ここまできたら明日もやりたいけど、SECCONの準備があるんだよな。

また、IPFactory OB Advent Calendar 2023の21日目の記事も兼ねています。