[Brainfuck]難解言語?Go言語で独自言語を作ってみる

この記事は約19分で読めます。

Brainfuckというプログラミング言語をご存知でしょうか?

今回はBrainfuckという言語を派生させて、
Go言語で独自言語を作ってみます。

それではいきましょう!

Brainfuckとは

Brainfuckとは難解プログラム言語の一つです。

Brainf*ckのように伏字で表記されることもあります。

実行する命令はたったの8種類しかありません。

しかし、8つの命令だけでチューリング完全なんです!

仕組みは単純です。

>ポインタをインクリメント (右にずらす)
<ポインタをデクリメント (左にずらす)
+ポインタが指す値をインクリメントする
ポインタが指す値をデクリメントする
.ポインタが指す値を出力
,入力から1バイト読み込んで、ポインタが指す値にその値を代入
[ポインタが指す値が0なら、後の]までジャンプ
]ポインタの指す値が0でなければ、前の[までジャンプ([]でwhileを示す)

これだけです。

挙動を詳しく知りたい方は

良いスライドがあるので、ご参照ください。

実用Brainf*ckプログラミング

分かりやすい記事

Brainf*ck

このBrainfuckで”Hello World!”と表示させるには

>+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.[-]>++++++++[<++
++>-]<.>+++++++++++[<+++++>-]<.>++++++++[<+++>-]<.+++.------.--------.[-]>
++++++++[<++++>-]<+.[-]++++++++++.

と書きます。

訳がわかりませんよね?

上の記事を見れば、なんとなくは分かると思います。

どのようなプログラムも基本的にアドレスとアドレスの指す値の配列のようなアドレス空間で管理されています。

それをBrainfuckではプログラム上で模倣しています。

ジョジョ言語知ってる?

Brainfuckは8つの命令しかないため、他の言語に比べて実装がしやすいです。

ジョジョ言語はBrainfuckの命令をjojoの名言に置換えている言語です。

これはRubyで書かれています。

今回はこれと同じ機能を

Go言語で実装します。

また、命令はドラゴンボールの悟空の名言です。

Go空語

とりあえず、今回作った

Go空語

で書いたプログラムは以下です!!

ryomak/brainfuck-go
Contribute to ryomak/brainfuck-go development by creating an account on GitHub.
地球はわからねえが、おめえはおらが守る。
悟飯 父ちゃんが生きてかえったらまた 釣りにでも行こうな…!
へへへ…オラ シッポはえてるから人間じゃないかもな…
俺がやらなきゃ、誰がやる!!
おめぇ強そうだな!
オッス!オッス!オッス!オッス!いやだ!クリリンは友だちだ!亀仙人のじいちゃんには世話になったんだ!棒よのびろ――っオッス!オッス!オッス!オッス!オッス!オッス!オッス!オッス!おめぇ強そうだな!界王拳!!クリリンのことかーーーーーーーーっおめぇ強そうだな!オッス!オッス!オッス!オッス!オッス!オッス!オッス!オッス!いやだ!クリリンは友だちだ!亀仙人のじいちゃんには世話になったんだ!棒よのびろ――っオッス!オッス!オッス!オッス!オッス!オッス!おめぇ強そうだな!界王拳!!クリリンのことかーーーーーーーーっ棒よのびろ――っオッス!オッス!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! オッス!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! オッス!オッス!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! オッス!オッス!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! 界王拳!!界王拳!!界王拳!!界王拳!!界王拳!!界王拳!!バイバイ、みんなバイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! 
バイバイ、みんなオッス!オッス!バイバイ、みんな棒よのびろ――っバイバイ、みんな
おめぇ強そうだな! 界王拳!!界王拳!!バイバイ、みんな
オッス!オッス!オッス!オッス!オッス!オッス!バイバイ、みんな棒よのびろ――っ
バイバイ、みんなおめぇ強そうだな! 界王拳!!界王拳!!界王拳!!界王拳!!界王拳!!界王拳!!バイバイ、みんなおめぇ強そうだな!
オッス!オッス!オッス!いやだ!クリリンは友だちだ!亀仙人のじいちゃんには世話になったんだ!棒よのびろ――っオッス!オッス!オッス!おめぇ強そうだな!界王拳!!クリリンのことかーーーーーーーーっ棒よのびろ――っ界王拳!!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! 界王拳!!界王拳!!界王拳!!界王拳!!界王拳!!界王拳!!界王拳!!バイバイ、みんなオッス!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! 界王拳!!バイバイ、みんなオッス!オッス!オッス!オッス!オッス!オッス!オッス!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! 
界王拳!!界王拳!!界王拳!!界王拳!!界王拳!!界王拳!!バイバイ、みんな界王拳!!
界王拳!!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! 
オッス!オッス!バイバイ、みんなオッス!オッス!オッス!オッス!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! 界王拳!!界王拳!!界王拳!!バイバイ、みんな界王拳!!界王拳!!界王拳!!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! オッス!オッス!オッス!バイバイ、みんな界王拳!!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! オッス!バイバイ、みんなオッス!オッス!オッス!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! 界王拳!!界王拳!!バイバイ、みんな界王拳!!界王拳!!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! オッス!オッス!バイバイ、みんなオッス!オッス!オッス!オッス!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! 
界王拳!!界王拳!!界王拳!!バイバイ、みんな界王拳!!界王拳!!界王拳!!界王拳!!
界王拳!!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな!
オッス!オッス!オッス!オッス!オッス!バイバイ、みんなオッス!バイバイ、みんな
棒よのびろ――っバイバイ、みんなおめぇ強そうだな! バイバイ、みんな界王拳!!界王拳!!界王拳!!界王拳!!界王拳!!
界王拳!!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! オッス!オッス!オッス!オッス!オッス!オッス!バイバイ、みんな界王拳!!界王拳!!界王拳!!界王拳!!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! オッス!オッス!オッス!オッス!バイバイ、みんなオッス!オッス!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! 界王拳!!バイバイ、みんな界王拳!!界王拳!!界王拳!!界王拳!!界王拳!!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! オッス!オッス!オッス!オッス!オッス!バイバイ、みんなオッス!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! バイバイ、みんな界王拳!!界王拳!!バイバイ、みんな

これで実は

100までの素数を表示するプログラムです。

実行結果

それでは、

作っていきましょう!

実装方針

実は実装方針が2つありました。

  • Goインタプリタを模倣する
  • jojo語と同じ正規表現で命令を取得する

Goのインタプリタを模倣する

こちらは、オライリー出版の有名な本で、Go言語でMonkeyという独自言語を作るという本です。実際の言語作成にも使われているコードが多く、とても学びが深くなります。

メリット

  • 文法をしっかり学べる
  • 独自言語としてクオリティが上がる

デメリット

  • 文法がしっかりしているため、無駄な文字を入れることができない。

jojo語と同じ正規表現で命令を取得する

読み込んだファイルの中から、正規表現を用いて、命令とマッチするものだけを読み取ります。

メリット

  • 無駄な文字を入れることができる
  • 解析が楽→(簡単にコマンドの配列が取得できる)

デメリット

  • インタプリタと比べて、設計が甘い

今回の方針

途中まで、インタプリタと同じように作っていたのですが、8つの言葉だけでは、シンプルすぎます。

Go空語を作るのであれば、命令には関係ない言葉も、色々織り交ぜたいです。

なので、後者の正規表現を用いていきます。

実践

ディレクトリ構成

.
├── bf
│   ├── config.go
│   ├── lexer.go
│   └── token.go
├── main.go
├── go.mod
├── go.sum
├── goku.bf
└── goku-token.toml
  • bf/config.go…tomlファイルから命令の割り当てを取得します。
  • bf/lexer.go…Go空語を解析していきます。
  • bf/token.go…8つの命令を定義しています。
  • goku.bf…Go空語で素数を出力するプログラムです。
  • goku-token.toml…各命令に対して割り当てを設定しているファイルです。

bfディレクトリ

package bf

import "github.com/BurntSushi/toml"

type ConfigData struct {
	Commands commands
}

var Config ConfigData

func LoadConfig(filePath string) error {
	_, err := toml.DecodeFile(filePath, &Config)
	if err != nil {
		return err
	}
	return nil
}

ここでは”goku-token.toml”を読み取って、commandsにマッピングしています。

comanndsは8つの命令のことで、token.goで定義されています。

スポンサーリンク

次に、lexer.goです。

package bf

import (
	"fmt"
	"os"
	"regexp"
)

type state struct {
	cs            *commands
	inputStr      string
	inputCommands []string
	memory        []byte
	pointer       int
}


func InitState(inputSrc string, c *commands, maxCommandNum int) *state {
	state := new(state)
	state.cs = c
	state.inputStr = inputSrc
	state.memory = make([]byte, maxCommandNum)
	state.pointer = 0
	state.parse()
	return state
}

func (s *state) parse() {
	r := regexp.MustCompile(`(` + s.cs.Ops() + `)`)
	s.inputCommands = r.FindAllString(s.inputStr, -1)
}

func (s *state) Start() {
	turn := 0
	for turn < len(s.inputCommands) {
		switch s.inputCommands[turn] {
		case s.cs.NEXT:
			s.pointer++
		case s.cs.PREV:
			s.pointer--
		case s.cs.INC:
			s.memory[s.pointer]++
		case s.cs.DEC:
			s.memory[s.pointer]--
		case s.cs.WRITE:
			fmt.Print(string(s.memory[s.pointer]))
		case s.cs.READ:
			r := make([]byte, 1)
			os.Stdin.Read(r)
			s.memory[s.pointer] = r[0]
		case s.cs.OPEN:
			if s.memory[s.pointer] == 0 {
				for depth := 1; depth > 0; {
					turn++
					nCommand := s.inputCommands[turn]
					if nCommand == s.cs.OPEN {
						depth++
					}
					if nCommand == s.cs.CLOSE {
						depth--
					}
				}
			}
		case s.cs.CLOSE:
			for depth := 1; depth > 0; {
				turn--
				nCommand := s.inputCommands[turn]
				if nCommand == s.cs.OPEN {
					depth--
				}
				if nCommand == s.cs.CLOSE {
					depth++
				}
			}
			turn--
		}
		turn++
	}
}

lexer.goが一番処理の肝です。

  • state構造体…これは,Go空語を解析するために使う構造体です。pointerとmemoryが、アドレス空間の模倣で、inputStrが読み取ったGo空語です。
  • parse()…ここでは、命令を取得する正規表現を実行します。inputStrから、命令を抜き出して、配列にし、inputCommandsに格納しています。
  • InitState()…state構造体を初期化します。初期化するときに、parse()を実行しています。
  • start()…ここが、コマンドを実行するロジックの肝です。与えられた、プログラムを逐次実行していきます。ここが、Brainfuckのロジックになります。
package bf

import (
	"regexp"
        "strings"
)

type commands struct {
	NEXT  string
	PREV  string
	INC   string
	DEC   string
	READ  string
	WRITE string
	OPEN  string
	CLOSE string
}

func (c *commands) Ops() string {
	//for regexp
	return strings.Join(c.list(), "|")
}

func (c *commands) list() []string {
	return []string{
		regexp.QuoteMeta(c.NEXT),
		regexp.QuoteMeta(c.PREV),
		regexp.QuoteMeta(c.INC),
		regexp.QuoteMeta(c.DEC),
		regexp.QuoteMeta(c.READ),
		regexp.QuoteMeta(c.WRITE),
		regexp.QuoteMeta(c.OPEN),
		regexp.QuoteMeta(c.CLOSE),
	}
}

ここではconfigでマッピングした命令の処理をまとめたファイルです。

  • Ops()…コマンドを”|”で結合して返しています。”|”は、正規表現で、”もしくは”の意味です。コマンドのどれかに当てはまる正規表現を作成するために使います。
  • list()…commands構造体をstringの配列で返す関数です。QuoteMeta()関数を用いることで、正規表現でエスケープ処理をしなければならない記号が与えられた場合、それをエスケープします。

main.go

実行するmainのファイルは以下です。

package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"

	"./bf"
)

var (
	tomlFile   *string
	selectFile *string
	help       *bool
)

func init() {
	selectFile = flag.String("f", "fizzbuzz.bf", "file")
	tomlFile = flag.String("t", "token.toml", "toml file")
	help = flag.Bool("usage", false, "help usage")

	flag.Parse()
	if err := bf.LoadConfig(*tomlFile); err != nil {
		panic(err)
	}
	if *help {
		fmt.Println("usage:")
		fmt.Println("go run main.go -t token.toml -f fizzbuzz.bf")
		return 
	}
}
func main() {
	inputSrc, err := ioutil.ReadFile(*selectFile)
	if err != nil {
		fmt.Printf("cant read file: %v\n", os.Args[1])
		panic(err)
	}
	state := bf.InitState(string(inputSrc), &bf.Config.Commands, 1000)
	state.Start()
}
  • -t でtomlファイル=命令記述ファイル(goku-token.toml)を選択
  • -f で実行ファイル(goku.bf)を選択

configファイルとGo空語

[Commands]
NEXT="おめぇ強そうだな!"
PREV ="棒よのびろ――っ"
INC  ="オッス!"
DEC  ="界王拳!!"
READ ="オラのすべてをこの拳にかける"
WRITE="バイバイ、みんな"
OPEN ="いやだ!クリリンは友だちだ!亀仙人のじいちゃんには世話になったんだ!"
CLOSE="クリリンのことかーーーーーーーーっ"

これがgoku-token.tomlの中身です。8つの命令に独自の言葉をマッピングしています。

地球はわからねえが、おめえはおらが守る。
悟飯 父ちゃんが生きてかえったらまた 釣りにでも行こうな…!
へへへ…オラ シッポはえてるから人間じゃないかもな…
俺がやらなきゃ、誰がやる!!
おめぇ強そうだな!
オッス!オッス!オッス!オッス!いやだ!クリリンは友だちだ!亀仙人のじいちゃんには世話になったんだ!棒よのびろ――っオッス!オッス!オッス!オッス!オッス!オッス!オッス!オッス!おめぇ強そうだな!界王拳!!クリリンのことかーーーーーーーーっおめぇ強そうだな!オッス!オッス!オッス!オッス!オッス!オッス!オッス!オッス!いやだ!クリリンは友だちだ!亀仙人のじいちゃんには世話になったんだ!棒よのびろ――っオッス!オッス!オッス!オッス!オッス!オッス!おめぇ強そうだな!界王拳!!クリリンのことかーーーーーーーーっ棒よのびろ――っオッス!オッス!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! オッス!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! オッス!オッス!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! オッス!オッス!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! 界王拳!!界王拳!!界王拳!!界王拳!!界王拳!!界王拳!!バイバイ、みんなバイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! 
バイバイ、みんなオッス!オッス!バイバイ、みんな棒よのびろ――っバイバイ、みんな
おめぇ強そうだな! 界王拳!!界王拳!!バイバイ、みんな
オッス!オッス!オッス!オッス!オッス!オッス!バイバイ、みんな棒よのびろ――っ
バイバイ、みんなおめぇ強そうだな! 界王拳!!界王拳!!界王拳!!界王拳!!界王拳!!界王拳!!バイバイ、みんなおめぇ強そうだな!
オッス!オッス!オッス!いやだ!クリリンは友だちだ!亀仙人のじいちゃんには世話になったんだ!棒よのびろ――っオッス!オッス!オッス!おめぇ強そうだな!界王拳!!クリリンのことかーーーーーーーーっ棒よのびろ――っ界王拳!!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! 界王拳!!界王拳!!界王拳!!界王拳!!界王拳!!界王拳!!界王拳!!バイバイ、みんなオッス!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! 界王拳!!バイバイ、みんなオッス!オッス!オッス!オッス!オッス!オッス!オッス!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! 
界王拳!!界王拳!!界王拳!!界王拳!!界王拳!!界王拳!!バイバイ、みんな界王拳!!
界王拳!!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! 
オッス!オッス!バイバイ、みんなオッス!オッス!オッス!オッス!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! 界王拳!!界王拳!!界王拳!!バイバイ、みんな界王拳!!界王拳!!界王拳!!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! オッス!オッス!オッス!バイバイ、みんな界王拳!!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! オッス!バイバイ、みんなオッス!オッス!オッス!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! 界王拳!!界王拳!!バイバイ、みんな界王拳!!界王拳!!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! オッス!オッス!バイバイ、みんなオッス!オッス!オッス!オッス!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! 
界王拳!!界王拳!!界王拳!!バイバイ、みんな界王拳!!界王拳!!界王拳!!界王拳!!
界王拳!!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな!
オッス!オッス!オッス!オッス!オッス!バイバイ、みんなオッス!バイバイ、みんな
棒よのびろ――っバイバイ、みんなおめぇ強そうだな! バイバイ、みんな界王拳!!界王拳!!界王拳!!界王拳!!界王拳!!
界王拳!!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! オッス!オッス!オッス!オッス!オッス!オッス!バイバイ、みんな界王拳!!界王拳!!界王拳!!界王拳!!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! オッス!オッス!オッス!オッス!バイバイ、みんなオッス!オッス!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! 界王拳!!バイバイ、みんな界王拳!!界王拳!!界王拳!!界王拳!!界王拳!!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! オッス!オッス!オッス!オッス!オッス!バイバイ、みんなオッス!バイバイ、みんな棒よのびろ――っバイバイ、みんなおめぇ強そうだな! バイバイ、みんな界王拳!!界王拳!!バイバイ、みんな

これがGo空語です。

いきなり独自言語で書くのは難しい!となると思うので、Brainfuckのコードを持ってきて、各命令、一括変換で置換えていきましょう!

実行する

-f がGo空語が書かれたファイル名

-t が8つの命令のマッピングです

$ go run main.go -f goku.bf -t goku-token.toml

まとめ

今回はGo言語でBrainfuckの仕組みを派生させた、独自言語を作ってみました。

案外、簡単に作れるので楽しいです!

暗号として、友人と共有してみるのもおすすめです!

合わせてGo言語で色々書いているので、覗いてみてくださいね!

 

Golang : Go言語 入門~ 難易度別 記事一覧 ~
この記事では、今まで解説したGo言語のプログラミング記事をまとめています!ここからどの記事にも飛べます!

 

それでは。

タイトルとURLをコピーしました