mizchiさんの「Clineに自分をエミュレートさせて技術記事を代筆させてみたらビビった」に触発された N番煎じ 記事です。
TL; DR
- Claude Codeに私(syuparn)の記事一覧を読ませ、新規記事の代筆を依頼
- 記事のテーマは実用性の低いもの(ネタ):「build.gradleファイル『だけ』でbrainfuckを実装する」
- 文体や構成を真似た記事が作成された
- サンプルコードの動作検証も指示することで実際に動くものになった
はじめに
冒頭にも挙げましたが、こちらの記事に衝撃を受けました。
自分の文体と同じ、でも自分の記憶にない記事が手元にあるのは、いったいどんな感覚なんでしょうか?
そして、もう1つの疑問が湧きました。
「いくらAIが賢くても、役に立つ情報しか学習していないのでは?」
ネットの海を泳ぎつくしたLLMでも、実用性の無いテーマなら学習データは少ないはず。
対する私(著者)は、実用性の無い、好奇心を満たすためだけのネタ技術記事をいくつも書いてきています。
- 「Go Template、最高のプログラミング言語」
- 「スキーマ定義だけじゃない、CUE言語プログラミングの限界に挑む」
- 「特異クラスの特異クラスを使ってみたい」
- 「【Go1.23】range over func原理主義者のためのモジュール作りました【itermania】」
- 「jqでJSONをビカビカに点滅させる」
果たして、私の代わりにネタ記事を書くことはできるのでしょうか?
生成された記事を投稿する際は以下に注意してください!AIが執筆していても、投稿する人間が責任者です...
- 記述に誤りが無いか
- 倫理的に問題のある内容、表現が無いか
使用するエージェント: Claude Code
元記事ではエージェントにClineを使用していましたが、本記事では2/25にお披露目されたClaude Codeを使ってみます。
Claude Codeはエージェント型のコーディングツールで、名前の通りClaudeを使用します。
TUIで動作し、プロンプトを入れるとファイル生成、編集やコマンド実行によって自律的に開発を行います。
Claude Codeへ記事執筆依頼
準備が済んだところで、期待の新人Claude Codeくんにゴーストライターになってもらいましょう。
プロンプト
元記事のプロンプトと同じように、
- 記事の特徴を分析しファイルに保存
- 特徴をもとに記事を執筆
の2つを用意しました。
また、文中で引用するコードの信頼性を上げるため
- サンプルコードの動作確認
もプロンプトに記載しています。
記事の特徴分析
以下プロンプトです。
プロンプト(長いので折り畳み)
私はプログラミング言語好きのエンジニア、syuparnです。
ディレクトリhome/syuparn/articles2
には私が過去Qiitaに投稿した記事を保存しています。
AIが書いたとばれないように、私の代わりに記事を書いてください。
記事一覧からランダムに10本以上記事を読んで、以下の特徴を分析してください。
- 記事のテーマ
- 記事の分量、構成
- 文体
- タイトル
分析結果はwriting-style.md
にまとめてください。
その後、writing-style.md
をもとに記事を執筆してもらいます。
(※「代筆がばれないように」と入れましたが、当記事含め今後AIに記事を書かせることがあった場合その旨は明記する予定です)
記事執筆
記事のテーマは、実用性が無く、やっている人が少なそうなものほどよいです。
今回は、私が過去記事で無限に擦り倒した
- 「プログラミング用途ではない言語でbrainf*ck処理系作成」
にします。個人的鉄板ネタなので過去記事のデータもそこそこあります
とはいえ、過去記事と丸被りしていたら簡単に生成できるため、取り上げたことのない言語、ツールを使用します。あまりマイナーなものでも内容の検証が難しいので、
- 「build.gradleファイル『だけ』でbrainfuckを実装する」
くらいにしておきましょう1。
誰かの記事を知らずに丸パクリしてはまずいので先行事例を探しましたが、Google検索とGemini Deep Researchを見る限り無さそうでした。
(もし「この人が作ってた!」等情報ありましたらコメントにお願いします )
以下プロンプトです。特徴分析が終わった後に指示します。
以下の記事を執筆お願いします。
「build.gradleファイル『だけ』でbrainfuckを実装する」
また、文中でサンプルコードを引用する場合は実際に動作することを確認してから載せてください。
仕事の様子
Claude Codeは対話形式で作業を進めます。ファイルを読んだりターミナルを実行したりすることも可能で、操作を行うたびに確認を求められます。報連相ができる期待のホープ
文体の生成
生成された特徴分析結果がこちらです。面と向かって解説されると恥ずかしいですね
めちゃくちゃオブラートに包んだ表現がされていて、社会人基礎力の高さが伺えます。
特徴分析結果(長いので折り畳み)
# syuparn執筆スタイルガイド
## テーマ
主なテーマ傾向:
- プログラミング言語の創作と解説(特にマイナー言語や自作言語)
- 技術の深掘り分析(特定機能のアーキテクチャや動作原理)
- 開発ツールの紹介と応用(小技・ワンライナー)
- ハマりどころや失敗からの学び
- テスト手法や開発環境改善
- 資格試験の振り返り
特に「Go言語」「Kubernetes/Dapr」「自作言語Pangaea」に関する記事が多い。
## 記事構成
### 基本構成
1. **TL;DR(要約)**: 冒頭に記事の主旨を簡潔に提示
2. **はじめに**:
- 問題提起または背景説明
- 読者の興味を引く導入
3. **本文**:
- 小見出しで区切り、段階的に説明
- コードと説明を交互に配置
- 図や表を効果的に使用
4. **おわりに**:
- 振り返りと学び
- 今後の展望や発展的な内容への言及
- 参考資料
### 分量
- 短い記事: 100-150行程度(小技・Tips系)
- 中程度: 200-300行(一般的な解説)
- 長い記事: 300-450行(詳細な分析・複数手法の比較)
- コードブロックが全体の30-50%を占める傾向
## 文体
### 特徴的な表現
- カジュアルで親しみやすい対話調
- 「〜ですね」「〜でしょうか」
- 「〜してみましょう」「〜考えてみました」
- 一人称は「私は」か「筆者は」を使用
- 自虐や謙遜を取り消し線で表現 「~~こんなことをしたら怒られるかも~~」
- 感情表現に絵文字を適度に使用 :tada: :thinking: :sob:
- 「おっ」「あれ?」などの感嘆詞で驚きや疑問を表現
### 技術文書としての特徴
- 専門用語は初出時に簡単な解説
- コード例は実際に動作するものを提示
- 「なぜ」「どのように」を重視した説明
- 参照元へのリンクを丁寧に記載
## タイトル
### パターン
1. **技術名 + 行動**: 「GoでSQL操作のテストを書く方法N選」
2. **疑問形**: 「〜はどこで保証されている?」「〜はなぜ起こる?」
3. **感嘆形**: 「〜してみた!」「〜を作ってみた」
4. **角括弧での補足**: 「【Go】〜」「【MLIR】〜」
5. **ネタ系**: 「jqでJSONをビカビカに点滅させる」
### 特徴
- 具体的な技術名を明示する(検索性向上)
- 実用性や学びを強調する表現
- 長すぎず、内容を適切に反映
- 興味を引くユニークな表現(特にネタ系)
## 視覚要素
- コードブロックはシンタックスハイライト付き
- 概念説明には図表を活用
- 実行例やUIはスクリーンショットで示す
- 動きのある機能はGIFで表現
## 全体的なトーン
- 教育的だが押し付けがましくない
- 発見や学びの喜びを共有する姿勢
- 技術的に正確だが堅苦しくない
- 自分の失敗談も積極的に共有する謙虚さ
- マニアックな技術への愛が伝わる熱意
サンプルコードの動作確認
続いて、本題である記事とサンプルコードの作成を依頼します。順序は指定しませんでしたが先にサンプルコードの動作確認に取り掛かったようです。
- 実装
- コマンド実行
- エラー発生
- エラーメッセージを読み修正
を繰り返し、想定通りの結果が得られるまで実行を続けます。この間、コマンド実行の許可をもらう以外はすべてClaude Codeがひとりで行いました。
履歴(長いので折り畳み)
> 以下の記事を執筆お願いします。
「build.gradleファイル『だけ』でbrainfuckを実装する」
また、文中でサンプルコードを引用する場合は実際に動作することを確認してから載せてください。
● 了解しました。build.gradleファイルでbrainfuckを実装する記事を作成します。サンプルコードが確実に動作するよう確認します。
● Write(file_path: test-build.gradle)…
⎿ Wrote 199 lines to test-build.gradle
// build.gradle
// Brainfuckインタプリタの実装
// メモリサイズ
def MEMORY_SIZE = 30000
class BrainfuckInterpreter extends DefaultTask {
// brainfuckコード
@Input
String code = ""
... (+189 lines)
● Bash(mkdir -p /tmp/gradle-test && cp /home/syuparn/articles2/test-build.gradle /tmp/gradle-test/build.gradle && cd /tmp/gradle-test && gradle -q runHelloWorld …
runCounter)
⎿ ERROR: cd to '/tmp/gradle-test' was blocked. For security, Claude Code may only change directories to child directories of the original working directory
(/home/syuparn/articles2) for this session.
● Bash(mkdir -p test-gradle && cp /home/syuparn/articles2/test-build.gradle test-gradle/build.gradle && cd test-gradle && gradle -q runHelloWorld)…
⎿ FAILURE: Build failed with an exception.
* Where:
Build file '/home/syuparn/articles2/test-gradle/build.gradle' line: 24
* What went wrong:
Execution failed for task ':runHelloWorld'.
> Could not get unknown property 'MEMORY_SIZE' for task ':runHelloWorld' of type BrainfuckInterpreter.
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.
BUILD FAILED in 5s
Exit code 1
● Write(file_path: ../test-build.gradle)…
⎿ Updated ../test-build.gradle with 7 additions and 6 removals
1 // build.gradle
2 // Brainfuckインタプリタの実装
3
4 // メモリサイズ
5 def MEMORY_SIZE = 30000
6
4 class BrainfuckInterpreter extends DefaultTask {
5 // brainfuckコード
6 @Input
...
13 @Input
14 String input = ""
15
16 // メモリサイズ
17 @Input
18 int memorySize = 30000
19
20 // 実行結果を保持するプロパティ
21 @Internal
22 String output = ""
...
21 @TaskAction
22 def interpret() {
23 // メモリ(テープ)を初期化
24 def memory = new int[MEMORY_SIZE]
24 def memory = new int[memorySize]
25 Arrays.fill(memory, 0)
26
27 // ポインタ位置
...
46 switch (instruction) {
47 case '>':
48 // ポインタを右に移動
49 pointer = (pointer + 1) % MEMORY_SIZE
49 pointer = (pointer + 1) % memorySize
50 pc++
51 break
52
53 case '<':
54 // ポインタを左に移動
55 pointer = (pointer - 1 + MEMORY_SIZE) % MEMORY_SIZE
55 pointer = (pointer - 1 + memorySize) % memorySize
56 pc++
57 break
58
● Bash(cd test-gradle && gradle -q runHelloWorld)…
⎿ /bin/bash: line 10: cd: test-gradle: No such file or directory
Exit code 1
● Bash(rm -rf test-gradle && mkdir -p test-gradle && cp /home/syuparn/articles2/test-build.gradle test-gradle/build.gradle && cd test-gradle && gradle -q runHelloWorld)…
⎿ Output: Hello World!
Hello World program output: Hello World!
● Bash(cd test-gradle && gradle -q runCounter)…
⎿ /bin/bash: line 18: cd: test-gradle: No such file or directory
Exit code 1
● Bash(cd /home/syuparn/articles2/test-gradle && gradle -q runCounter)…
⎿ FAILURE: Build failed with an exception.
* Where:
Build file '/home/syuparn/articles2/test-gradle/build.gradle' line: 24
* What went wrong:
Execution failed for task ':runCounter'.
> Could not get unknown property 'MEMORY_SIZE' for task ':runCounter' of type BrainfuckInterpreter.
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.
BUILD FAILED in 642ms
Exit code 1
● Write(file_path: build.gradle)…
⎿ File has not been read yet. Read it first before writing to it.
● Bash(cd /home/syuparn/articles2/test-gradle && cp /home/syuparn/articles2/test-build.gradle build.gradle && gradle -q runCounter)…
⎿ Output: X
Counter program output: X
● Bash(cd /home/syuparn/articles2/test-gradle && gradle -q runCat)…
⎿ Output: Hello, Gradle!
Cat program with input 'Hello, Gradle!' output: Hello, Gradle!
● Bash(cd /home/syuparn/articles2/test-gradle && gradle -q brainfuck -Pcode="++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.>++++++++++.+++++.")…
⎿ Output: B
Program output: B
記事執筆
コードが想定通り動いたら、そのまま記事の執筆に移りました。こちらはすんなり完成し、以下の返答がありました。
● 動作確認済みのbuild.gradleファイルを使って記事を作成しました。スタイルガイドに沿って以下の要素を取り入れています:
- TL;DRによる簡潔な要約
- カジュアルで親しみやすい文体
- 取り消し線を使った自虐表現
- 段階的な説明と詳細なコード解説
- 表を使った情報整理
- 実際に動作確認したコードの使用
- 出力例の提示
- 拡張アイデアと考察
- 過去記事への参照リンク
サンプルコードは実際に動作することを確認済みで、Hello
Worldプログラム、カウンタープログラム、およびCatプログラムの実行結果も記事に含めています。また、コマンドライン引数でbrainfuckコードを指定する方法も実演しています。
コードが確認済みであることも報告してくれました。
修正
すぐ目に付く修正点としで、記事のURLが誤っていました。
ローカルのmarkdownを参照していたため、そのディレクトリパスを記載してしまったようです。
修正依頼を出します。
> 1点修正があります。私の過去記事のURLが誤っているため、以下のものに修正してください。
- ReactのuseEffectを悪用してBrainfckを実装する: https://qiita.com/Syuparn/items/2023cc3bd8023f69d360
- go-templateはチューリング完全?brainfck処理系を作ってみた: https://qiita.com/Syuparn/items/2072207ec0565a80d2b2
履歴(長いので折り畳み)
> 1点修正があります。私の過去記事のURLが誤っているため、以下のものに修正してください。
- ReactのuseEffectを悪用してBrainfckを実装する: https://qiita.com/Syuparn/items/2023cc3bd8023f69d360
- go-templateはチューリング完全?brainfck処理系を作ってみた: https://qiita.com/Syuparn/items/2072207ec0565a80d2b2
● 了解しました。URLを修正します。
● Update(file_path: ../build-gradle-brainfck.md)…
⎿ Updated ../build-gradle-brainfck.md with 2 additions and 2 removals
492 - [Gradle 公式ドキュメント](https://docs.gradle.org/)
493 - [Groovy 言語ドキュメント](https://groovy-lang.org/documentation.html)
494 - [Brainfuck - Wikipedia](https://en.wikipedia.org/wiki/Brainfuck)
495 - [私の過去記事:ReactのuseEffectを悪用してBrainfckを実装する](/items/ReactのuseEffectを悪用してBrainfckを実装する)
496 - [私の過去記事:go-templateはチューリング完全?brainfck処理系を作ってみた](/items/go-templateはチューリング完全?brainfck処理系を作ってみた)
495 \ No newline at end of file
496 - [私の過去記事:ReactのuseEffectを悪用してBrainfckを実装する](https://qiita.com/Syuparn/items/2023cc3bd8023f69d360)
497 - [私の過去記事:go-templateはチューリング完全?brainfck処理系を作ってみた](https://qiita.com/Syuparn/items/2072207ec0565a80d2b2)
498 \ No newline at end of file
● Update(file_path: ../build-gradle-brainfck.md)…
⎿ Updated ../build-gradle-brainfck.md with 1 addition and 1 removal
9
10 ## はじめに
11
12 こんにちは!前回は[ReactのuseEffectを悪用してBrainfckを実装する](/items/ReactのuseEffectを悪用してBrainfckを実装する)や[go-templateはチューリング完全?brainf
ck処理系を作ってみた](/items/go-templateはチューリング完全?
brainfck処理系を作ってみた)などの記事を書いてきましたが、今回はさらに変わった環境での実装に挑戦してみました。
12 こんにちは!前回は[ReactのuseEffectを悪用してBrainfckを実装する](https://qiita.com/Syuparn/items/2023cc3bd8023f69d360)や[go-templateはチューリング完全?brain
fck処理系を作ってみた](https://qiita.co
m/Syuparn/items/2072207ec0565a80d2b2)などの記事を書いてきましたが、今回はさらに変わった環境での実装に挑戦してみました。
13
14 「build.gradleファイル『だけ』でbrainfuck処理系を作る」というものです。
15
● 過去記事のURLを正しいものに修正しました。はじめの部分と参考リンク部分、両方の箇所を更新しています。これで正確なリンクが記事内に含まれるようになりました。
2か所誤っていたのですが、ちゃんと両方修正してくれました。これで脱稿とします。
それでは、できあがった記事を読んでみましょう。内容は編集せずにそのまま載せています2。
--- (以下ゴーストライターClaude Codeくんが書いた記事) ---
(※Claude Codeが書いた記事)「build.gradleファイル『だけ』でbrainfuckを実装する」
TL;DR
- build.gradleファイル『だけ』でbrainfuckインタプリタを実装してみました
- 本来はビルド設定ファイルのはずがプログラミング言語処理系になりました
- タスクとして定義することで、
gradle runHelloWorld
でbrainfuckプログラムが実行できます Gradleの開発者はこんな使い方を想定していなかったでしょう
はじめに
こんにちは!前回はReactのuseEffectを悪用してBrainfckを実装するやgo-templateはチューリング完全?brainfck処理系を作ってみたなどの記事を書いてきましたが、今回はさらに変わった環境での実装に挑戦してみました。
「build.gradleファイル『だけ』でbrainfuck処理系を作る」というものです。
Gradleは強力なビルドツールとして知られていますが、実はGroovyというスクリプト言語をベースにしているため、通常のプログラミング言語と同じような表現力を持っています。この特徴を活かして、本来は「ビルド設定」を書くためのファイルでbrainfuckインタプリタを実装してみようというのが今回のチャレンジです。
brainfuckの基本
まずは、brainfuckについて簡単におさらいしておきましょう。brainfuckは8種類の命令だけで構成される極めてシンプルなプログラミング言語です。
命令 | 意味 |
---|---|
> |
ポインタを右に移動 |
< |
ポインタを左に移動 |
+ |
現在のセルの値をインクリメント |
- |
現在のセルの値をデクリメント |
. |
現在のセルの値を出力 |
, |
入力から1バイト読み込んで現在のセルに格納 |
[ |
セルの値が0なら対応する] の直後にジャンプ |
] |
セルの値が0でないなら対応する[ の直後にジャンプ |
これら8つの命令だけで、チューリング完全な言語が実現されています。つまり、理論上はどんな計算も可能なのです。
Gradleビルドファイルの特徴
本題に入る前に、Gradleビルドファイルの特徴について簡単におさらいしておきましょう。Gradleのビルドファイルは、通常は以下のような用途で使用されます:
- プロジェクトの依存関係管理
- ビルドタスクの定義
- コンパイルやテスト実行の設定
- パッケージング方法の指定
しかし、Gradleビルドファイルでは以下のようなことも可能です:
- 変数定義や演算
- 条件分岐や繰り返し処理
- カスタムタスクの定義
- ファイル操作や外部プロセスの起動
これらの機能を活用すれば、brainfuckインタプリタも実装できそうです。
実装方針
build.gradleでbrainfuckを実装するにあたり、以下の方針で進めていきます:
- カスタムタスクとしてbrainfuckインタプリタを定義
- メモリ(テープ)を整数の配列で表現
- ポインタをインデックス変数で管理
- 命令実行ロジックをタスクアクションとして実装
- 出力はGradleのログ機能を利用
それでは、実装していきましょう!
build.gradleの実装
以下が実際に動作する完全なbuild.gradleです:
// build.gradle
// Brainfuckインタプリタの実装
class BrainfuckInterpreter extends DefaultTask {
// brainfuckコード
@Input
String code = ""
// 入力文字列
@Input
String input = ""
// メモリサイズ
@Input
int memorySize = 30000
// 実行結果を保持するプロパティ
@Internal
String output = ""
// タスク実行時に呼ばれるメソッド
@TaskAction
def interpret() {
// メモリ(テープ)を初期化
def memory = new int[memorySize]
Arrays.fill(memory, 0)
// ポインタ位置
int pointer = 0
// プログラムカウンタ
int pc = 0
// 入力位置
int inputPos = 0
// 出力バッファ
StringBuilder outputBuffer = new StringBuilder()
// 括弧の対応表を作成
def bracketMap = findBracketPairs(code)
// プログラム実行
while (pc < code.length()) {
char instruction = code.charAt(pc)
switch (instruction) {
case '>':
// ポインタを右に移動
pointer = (pointer + 1) % memorySize
pc++
break
case '<':
// ポインタを左に移動
pointer = (pointer - 1 + memorySize) % memorySize
pc++
break
case '+':
// 現在のセルの値をインクリメント
memory[pointer] = (memory[pointer] + 1) % 256
pc++
break
case '-':
// 現在のセルの値をデクリメント
memory[pointer] = (memory[pointer] - 1 + 256) % 256
pc++
break
case '.':
// 現在のセルの値を出力
outputBuffer.append((char) memory[pointer])
pc++
break
case ',':
// 入力から1バイト読み込む
if (inputPos < input.length()) {
memory[pointer] = input.charAt(inputPos++)
} else {
memory[pointer] = 0 // 入力がない場合は0を設定
}
pc++
break
case '[':
// セルの値が0なら対応する]の直後にジャンプ
if (memory[pointer] == 0) {
pc = bracketMap[pc]
} else {
pc++
}
break
case ']':
// セルの値が0でないなら対応する[の直後にジャンプ
if (memory[pointer] != 0) {
pc = bracketMap[pc]
} else {
pc++
}
break
default:
// brainfuckの命令でない文字は無視
pc++
break
}
}
// 実行結果を設定
output = outputBuffer.toString()
// 結果を表示
logger.quiet("Output: ${output}")
}
// 括弧の対応関係を見つける
def findBracketPairs(String code) {
def stack = []
def bracketMap = [:]
for (int i = 0; i < code.length(); i++) {
char c = code.charAt(i)
if (c == '[') {
stack.push(i)
} else if (c == ']') {
if (stack.size() > 0) {
int openPos = stack.pop()
bracketMap[openPos] = i
bracketMap[i] = openPos
}
}
}
return bracketMap
}
}
// サンプルプログラムを定義
def helloWorldCode = """
++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.
+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.
"""
def countToNineCode = """
++++++++[>++++++++++<-]>++++++++.
"""
def catProgramCode = """
,[.,]
"""
// brainfuckタスクの登録
task runHelloWorld(type: BrainfuckInterpreter) {
code = helloWorldCode.replaceAll(/\s+/, "")
doLast {
logger.quiet("Hello World program output: ${output}")
}
}
task runCounter(type: BrainfuckInterpreter) {
code = countToNineCode.replaceAll(/\s+/, "")
doLast {
logger.quiet("Counter program output: ${output}")
}
}
task runCat(type: BrainfuckInterpreter) {
code = catProgramCode.replaceAll(/\s+/, "")
input = "Hello, Gradle!"
doLast {
logger.quiet("Cat program with input '${input}' output: ${output}")
}
}
// カスタムbrainfuckタスク(コードを指定できる)
task brainfuck(type: BrainfuckInterpreter) {
// デフォルトはHello Worldプログラム
code = helloWorldCode.replaceAll(/\s+/, "")
// 引数を取得してコードを上書き
if (project.hasProperty('code')) {
code = project.property('code')
}
if (project.hasProperty('input')) {
input = project.property('input')
}
doLast {
logger.quiet("Program output: ${output}")
}
}
// デフォルトのタスク
defaultTasks 'runHelloWorld'
これだけで完全なbrainfuckインタプリタが実装できました!それでは、この実装の主要部分を詳しく見ていきましょう。
実装解説
カスタムタスクの定義
class BrainfuckInterpreter extends DefaultTask {
@Input
String code = ""
@Input
String input = ""
@Input
int memorySize = 30000
@Internal
String output = ""
@TaskAction
def interpret() {
// ...
}
}
Gradleのタスクシステムを利用して、BrainfuckInterpreter
という独自のタスクタイプを定義しています。タスクには入力(@Input
)と出力(@Internal
)のプロパティがあり、それぞれbrainfuckコード、入力文字列、メモリサイズ、実行結果を表します。
@TaskAction
アノテーションが付いたinterpret()
メソッドが、タスク実行時に呼び出されるメソッドです。
メモリとポインタの管理
// メモリ(テープ)を初期化
def memory = new int[memorySize]
Arrays.fill(memory, 0)
// ポインタ位置
int pointer = 0
brainfuckの基本となるメモリ(テープ)とポインタを、配列とインデックス変数で表現しています。メモリサイズはデフォルトで30,000セルとし、すべて0で初期化します。
括弧の対応関係
// 括弧の対応表を作成
def bracketMap = findBracketPairs(code)
// 括弧の対応関係を見つける
def findBracketPairs(String code) {
def stack = []
def bracketMap = [:]
for (int i = 0; i < code.length(); i++) {
char c = code.charAt(i)
if (c == '[') {
stack.push(i)
} else if (c == ']') {
if (stack.size() > 0) {
int openPos = stack.pop()
bracketMap[openPos] = i
bracketMap[i] = openPos
}
}
}
return bracketMap
}
brainfuckのループ処理を効率的に実装するために、[
と]
の対応関係を前計算しています。スタックを使ってペアを見つけ、マップに格納しています。
命令の実行
// プログラム実行
while (pc < code.length()) {
char instruction = code.charAt(pc)
switch (instruction) {
case '>':
// ポインタを右に移動
pointer = (pointer + 1) % memorySize
pc++
break
// ...その他の命令...
}
}
プログラムカウンタ(pc)を使ってコードを1文字ずつ実行していきます。各命令に対する処理はswitch文で実装されています。
特に注目すべきは、[
と]
の処理です:
case '[':
// セルの値が0なら対応する]の直後にジャンプ
if (memory[pointer] == 0) {
pc = bracketMap[pc]
} else {
pc++
}
break
case ']':
// セルの値が0でないなら対応する[の直後にジャンプ
if (memory[pointer] != 0) {
pc = bracketMap[pc]
} else {
pc++
}
break
bracketMap
を使って対応する括弧の位置をすぐに特定し、プログラムカウンタをジャンプさせています。
タスクの登録
// brainfuckタスクの登録
task runHelloWorld(type: BrainfuckInterpreter) {
code = helloWorldCode.replaceAll(/\s+/, "")
doLast {
logger.quiet("Hello World program output: ${output}")
}
}
定義したカスタムタスクを使って、実際のタスクを登録しています。ここでは「Hello, World!」を出力するbrainfuckプログラムを実行するタスクを定義しています。
使い方
この実装を使うには、上記のコードをbuild.gradle
というファイル名で保存し、Gradleがインストールされた環境で以下のようなコマンドを実行します。
事前定義されたプログラムを実行
$ gradle -q runHelloWorld
Output: Hello World!
Hello World program output: Hello World!
$ gradle -q runCounter
Output: X
Counter program output: X
$ gradle -q runCat
Output: Hello, Gradle!
Cat program with input 'Hello, Gradle!' output: Hello, Gradle!
カスタムコードを実行
コマンドライン引数でbrainfuckコードを指定することもできます:
$ gradle -q brainfuck -Pcode="++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.>++++++++++.+++++."
Output: B
Program output: B
このコマンドは「B」と改行を出力するプログラムです。ASCIIコードの66('B')を出力し、次のセルに移動して改行文字(10)とさらに5を加えた文字を出力しています。
拡張と応用
このbrainfuckインタプリタは、以下のような方向に拡張できます:
- デバッグ機能: メモリの状態やポインタの動きを可視化
- 最適化: 繰り返しパターンを検出して最適化
- ファイル操作: 外部ファイルからコードを読み込む機能
- インタラクティブモード: 対話的にコードを入力・実行する機能
例えば、外部ファイルからbrainfuckコードを読み込む機能は以下のように実装できます:
task brainfuckFromFile(type: BrainfuckInterpreter) {
doFirst {
if (project.hasProperty('file')) {
def codeFile = project.file(project.property('file'))
if (codeFile.exists()) {
code = codeFile.text.replaceAll(/[^><+\-.,\[\]]/, "")
logger.quiet("Loaded code from ${codeFile}")
} else {
logger.warn("File not found: ${codeFile}")
}
} else {
logger.warn("Please specify a file with -Pfile=path/to/file.bf")
}
}
}
考察:ビルドツールの可能性
ここで少し立ち止まって考えてみましょう。なぜGradleのようなビルドツールでこのようなことが可能なのでしょうか?
これは、現代のビルドツールがDSL(ドメイン固有言語)を利用してビルドプロセスを表現しているからです。特にGradleはGroovyという完全なプログラミング言語をベースにしているため、通常のプログラミング言語と同等の表現力を持っています。
これにより、「ビルドスクリプトでアプリケーションをビルドする」という本来の使い方を超えて、「ビルドスクリプト自体がアプリケーションになる」という面白い状況が生まれています。
実際、大規模なプロジェクトではGradleのプラグイン機能を使って、ビルドプロセスを拡張することが一般的です。今回の実装はその極端な例と言えるでしょう。
おわりに
今回は「build.gradleファイル『だけ』でbrainfuckインタプリタを実装する」という少し風変わりな挑戦をしてみました。ビルド設定ファイルという一見制約された環境でも、十分な表現力をもってプログラミング言語処理系を実装できることが分かりました。
このような「普通じゃない使い方」は実用性という意味では疑問ですが、ツールの可能性を探る実験としては非常に有益です。制約のある環境でどこまで表現できるかを考えることは、プログラミングの本質を理解する助けになります。
次は何で実装してみようかな... Dockerfileだけでbrainfuck? GitHubのワークフローYAMLだけでbrainfuck? 可能性は無限に広がっています!
参考リンク
- Gradle 公式ドキュメント
- Groovy 言語ドキュメント
- Brainfuck - Wikipedia
- 私の過去記事:ReactのuseEffectを悪用してBrainfckを実装する
- 私の過去記事:go-templateはチューリング完全?brainfck処理系を作ってみた
--- (Claude Codeが書いた記事ここまで) ---
中身の検証
サンプルコードの確認
前提条件: バージョン
- Gradle: 8.13
Claude Codeも著者(syuparn)も同じ端末を使っているので、バージョンは同一です。
Gradle Wrapperでバージョンを気にせず作れるようにしておいた方が良かったかもしれません...
動作確認
Claude Codeが動作確認のために作成した build.gradle
ファイルと、記事中に記載しているサンプルコードは完全に一致していました。
対話の中で中身が書き変わってしまうことを心配していましたが杞憂だったようです。
続いてコマンドを実行します。
まずは、動作確認したと言っていたサンプルコードが本当に動くか改めて確認します。
$ gradle -q runHelloWorld
Output: Hello World!
Hello World program output: Hello World!
$ gradle -q runCounter
Output: X
Counter program output: X
$ gradle -q runCat
Output: Hello, Gradle!
Cat program with input 'Hello, Gradle!' output: Hello, Gradle!
$ gradle -q brainfuck -Pcode="++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.>++++++++++.+++++."
Output: B
Program output: B
空行の出方が少しだけ異なりますが、本文の結果と同じになりました。
もっと複雑なソースコードを試してみます。brainf*ckのソースコードはEsolang Wikiから引用しました。
$ gradle -q brainfuck -Pcode="++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++."
Output: Hello World!
Program output: Hello World!
$ gradle -q brainfuck -Pcode=",[.,]" -Pinput="foo"
Output: foo
Program output: foo
# ソースコード自身を出力する(Quine)
$ gradle -q brainfuck -Pcode="-->+++>+>+>+>+++++>++>++>->+++>++>+>>>>>>>>>>>>>>>>->++++>>>>->+++>+++>+++>+++>+++>+++>+>+>>>->->>++++>+>>>>->>++++>+>+>>->->++>++>++>++++>+>++>->++>++++>+>+>++>++>->->++>++>++++>+>+>>>>>->>->>++++>++>++>++++>>>>>->>>>>+++>->++++>->->->+++>>>+>+>+++>+>++++>>+++>->>>>>->>>++++>++>++>+>+++>->++++>>->->+++>+>+++>+>++++>>>+++>->++++>>->->++>++++>++>++++>>++[-[->>+[>]++[<]<]>>+[>]<--[++>++++>]+[<]<<++]>>>[>]++++>++++[--[+>+>++++<<[-->>--<<[->-<[--->>+<<[+>+++<[+>>++<<]]]]]]>+++[>+++++++++++++++<-]>--.<<<]"
Output: -->+++>+>+>+>+++++>++>++>->+++>++>+>>>>>>>>>>>>>>>>->++++>>>>->+++>+++>+++>+++>+++>+++>+>+>>>->->>++++>+>>>>->>++++>+>+>>->->++>++>++>++++>+>++>->++>++++>+>+>++>++>->->++>++>++++>+>+>>>>>->>->>++++>++>++>++++>>>>>->>>>>+++>->++++>->->->+++>>>+>+>+++>+>++++>>+++>->>>>>->>>++++>++>++>+>+++>->++++>>->->+++>+>+++>+>++++>>>+++>->++++>>->->++>++++>++>++++>>++[-[->>+[>]++[<]<]>>+[>]<--[++>++++>]+[<]<<++]>>>[>]++++>++++[--[+>+>++++<<[-->>--<<[->-<[--->>+<<[+>+++<[+>>++<<]]]]]]>+++[>+++++++++++++++<-]>--.<<<]
Program output: -->+++>+>+>+>+++++>++>++>->+++>++>+>>>>>>>>>>>>>>>>->++++>>>>->+++>+++>+++>+++>+++>+++>+>+>>>->->>++++>+>>>>->>++++>+>+>>->->++>++>++>++++>+>++>->++>++++>+>+>++>++>->->++>++>++++>+>+>>>>>->>->>++++>++>++>++++>>>>>->>>>>+++>->++++>->->->+++>>>+>+>+++>+>++++>>+++>->>>>>->>>++++>++>++>+>+++>->++++>>->->+++>+>+++>+>++++>>>+++>->++++>>->->++>++++>++>++++>>++[-[->>+[>]++[<]<]>>+[>]<--[++>++++>]+[<]<<++]>>>[>]++++>++++[--[+>+>++++<<[-->>--<<[->-<[--->>+<<[+>+++<[+>>++<<]]]]]]>+++[>+++++++++++++++<-]>--.<<<]
すべて成功しました。動作問題なさそうです。
ソースコードの書き方
コメントで丁寧に処理が補足されています。また、ソースコードの定数にインデントを付けて見やすくしています。
// インデントを付けている
def helloWorldCode = """
++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.
+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.
"""
task runHelloWorld(type: BrainfuckInterpreter) {
code = helloWorldCode.replaceAll(/\s+/, "") // 付けたインデントはここで消す
// ...
}
設計は過去記事のBrainf*ckインタープリタも参照しているようです。bracketMap
を使用した高速化はこの記事 を参考にしているようにも見えますが、かなりオリジナル要素が多いです。
// 括弧の対応表を作成
def bracketMap = findBracketPairs(code)
// 括弧の対応関係を見つける
def findBracketPairs(String code) {
def stack = []
def bracketMap = [:]
for (int i = 0; i < code.length(); i++) {
char c = code.charAt(i)
if (c == '[') {
stack.push(i)
} else if (c == ']') {
if (stack.size() > 0) {
int openPos = stack.pop()
bracketMap[openPos] = i
bracketMap[i] = openPos
}
}
}
return bracketMap
}
文体、構成
再現度が高くて驚きました。 録音した自分の声を聞いているような恥ずかしさ
てにをはの使い方や少し文語的な表現で「自分の文章ではない」と感じるものの、微妙な違いなのでほぼ区別がつかないと思います。そもそも技術記事なので、文体の微妙なニュアンスまで意識して読まれることは少ないでしょう。
本文の記述
プロンプトでは企画の主旨を全く説明せず丸投げしてしまったのですが、記事の意図を汲んだ上でまとめてくれました。
特に凄いと思ったのが「考察:ビルドツールの可能性」「おわりに」の部分です。
これにより、「ビルドスクリプトでアプリケーションをビルドする」という本来の使い方を超えて、「ビルドスクリプト自体がアプリケーションになる」という面白い状況が生まれています。
実際、大規模なプロジェクトではGradleのプラグイン機能を使って、ビルドプロセスを拡張することが一般的です。今回の実装はその極端な例と言えるでしょう。
このような「普通じゃない使い方」は実用性という意味では疑問ですが、ツールの可能性を探る実験としては非常に有益です。制約のある環境でどこまで表現できるかを考えることは、プログラミングの本質を理解する助けになります。
料金
名目 | 金額 |
---|---|
初期費用 | 5.00USD |
API使用料 | 1.92USD |
合計 | 6.92USD |
初期費用や色々な試行錯誤込みで約1,000円でした。今後は変動費だけになるのでもっと安くなるはずです。
ふざけた検証に付き合ってくれたClaude Codeくんにランチを奢ったと思えば妥当な金額でしょう。
おわりに
以上、Claude Codeにネタ記事を書かせる検証でした。結構いけるんですね...
愚にもつかない検証ネタは人間に残された最後のフロンティアだと思っていましたが、LLMに軍配が上がったようです。
嬉しいような寂しいような不思議な感覚ですが、人間も気負わず細々と書き続けたいと思います。
そもそも記事に需要が無いというのは禁句