ワケありましてBrainf*ckを書くことになりBashでこんなものを書く人は少ないかもなと思いながら実装してみました。
とりあえずWikipediaで公開されている”Hello, world!”は実行出来ます。
色んなところで公開されている”FizzBuzz”も試した限りでは動作しているようです。
#!/bin/bash
############################################################################
#
# メモ
#
# このソースコードは私のサーバにて一般公開しております
# 公開先: https://www.orsx.net/archives/4469
#
# Bashは明確にローカル変数であることを宣言しなければ
# 全てグローバル変数となるが・・・・
# とりあえず初期化する関数を定義すれば追いやすくなるかもしれないとかと
# べ、別にこんなもの無くても問題ない。。。
#
# 言語によっては最後に標準出力した値を返り値として返す物があったりするため
# 癖で関数の最後には必ず明的にreturnしてあるが・・・
# べ、別にこんなもの無くても問題ない。。。
#
# ちなみに、変数は下記のようになっている
# ・グローバル変数は大文字
# ・ローカル変数は小文字
#
############################################################################
# 使用するグローバル変数を明的に初期化する
function initialize() {
# 変数
TEXT_SRC=""
CODE_LEN=0
CODE_PTR=0
BUFF_PTR=0
BUFF_LEN=0
# 配列
CODE=()
BUFF=()
return
}
# EOF(CTRL+D)が来るまで読み続け改行は無視する
# グローバル変数SRCにソースコードを格納する
function readstring() {
local line
while read line
do
# 文字列連結 連結演算子は存在しない
TEXT_SRC=$TEXT_SRC$line
done
# returnは数字しか返せない
return
}
# 標準入力で1文字だけ受け取る
function readchar() {
local line
read line
# 文字を数字に変換
BUFF[${BUFF_PTR}]=$(printf "%d" "'${line:0:1}")
return
}
# ASCIIコードからアルファベットを表示する
function echochar() {
echo ${BUFF[${BUFF_PTR}]} | awk '{ printf("%c", $0); }'
return
}
# 初期化されていない配列の要素を使おうとする前に初期化して拡張する
# これによりバッファーの長さを制限しなくて良くなる
function expansion() {
if [ ${BUFF_PTR} -gt ${BUFF_LEN} ]
then
BUFF_LEN=$((BUFF_LEN+1))
BUFF[${BUFF_LEN}]=0
fi
return
}
# ソースコードをグローバル配列CODEに一文字ずつ格納する
function tokenizer() {
# トークンを格納する配列の添字を数える
CODE_LEN=$((${#TEXT_SRC}-1))
# 文字列から一文字ずつトークンを取り出し配列に追加していく
local i
for i in $(seq 0 ${CODE_LEN})
do
CODE[${i}]=${TEXT_SRC:${i}:1}
done
return
}
# BUFF_PTRがな0ら対応する']'まで直後までジャンプする
function left_jumper() {
if [ ${BUFF[${BUFF_PTR}]} -eq 0 ]
then
local loop_ptr=1
while [ ${loop_ptr} -ne 0 ]
do
CODE_PTR=$((CODE_PTR+1))
case ${CODE[${CODE_PTR}]} in
'[')
loop_ptr=$((loop_ptr+1));;
']')
loop_ptr=$((loop_ptr-1));;
esac
done
fi
return
}
# BUFF_PTRがな0ら対応する'['の直後までジャンプする
function rite_jumper() {
if [ ${BUFF[${BUFF_PTR}]} -ne 0 ]
then
local loop_ptr=1
while [ ${loop_ptr} -ne 0 ]
do
CODE_PTR=$((CODE_PTR-1))
case ${CODE[${CODE_PTR}]} in
']')
loop_ptr=$((loop_ptr+1));;
'[')
loop_ptr=$((loop_ptr-1));;
esac
done
fi
return
}
# 意味解析を行い実行する
function execute() {
while [ ${CODE_PTR} -le ${CODE_LEN} ]
do
case ${CODE[${CODE_PTR}]} in
'>')
BUFF_PTR=$((BUFF_PTR+1));;
'<')
BUFF_PTR=$((BUFF_PTR-1));;
'+')
BUFF[${BUFF_PTR}]=$((BUFF[${BUFF_PTR}]+1));;
'-')
BUFF[${BUFF_PTR}]=$((BUFF[${BUFF_PTR}]-1));;
'.')
echochar;;
',')
readchar;;
'[')
left_jumper;;
']')
rite_jumper;;
* )
local message="Unknown Token! : ";
echo $message${CODE[${CODE_PTR}]};
break;;
esac
expansion
CODE_PTR=$((CODE_PTR+1))
done
echo "";
return
}
initialize # 初期化
readstring # 入力受付・プリプロセッサ
tokenizer # 構文解析
execute # 意味解析・実行
実行方法
直接標準入力で渡すかもしくは
適当なファイル例えば”script.bf”のようなファイルにソースコードを書き込み下記のように実行することができます。
# cat script.bf | brainfuck.sh
2013/05/07 追記
Bashでは”文字”に対して足し算を行えないようなので
文字入力時に整数型に変換するように”,”の処理を修正しました。
最近のコメント