昨日に続きまして、 Rubyで作る奇妙なプログラミング言語 ~Esoteric Language~にて紹介した本の中から、「Brainf*ck」言語のJava実装です。
Brainfuckの言語仕様
実行可能な命令は「8つ」のみである。
- > ポインタをインクリメントする。ポインタをptrとすると、C言語の「ptr++;」に相当する。
- < ポインタをデクリメントする。C言語の「ptr--;」に相当。
- + ポインタが指す値をインクリメントする。C言語の「(*ptr)++;」に相当。
- - ポインタが指す値をデクリメントする。C言語の「(*ptr)--;」に相当。
- . ポインタが指す値を出力する。C言語の「putchar(*ptr);」に相当。
- , 1バイトを入力してポインタが指す値に代入する。C言語の「*ptr=getchar();」に相当。
- [ ポインタが指す値が0なら、対応する ] の直後までジャンプする。C言語の「while(*ptr){」に相当。
- ] ポインタが指す値が0でないなら、対応する [ にジャンプする。C言語の「}」に相当。
今日は初めから独自実装。
package st.happy_camper.esoteric.brainfuck;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import st.happy_camper.esoteric.util.Argf;
/**
* @author ueshin
*/
public class Brainfuck {
private interface CommandProcessor {
void process();
}
private final Map<Character, CommandProcessor> processors = new HashMap<Character, CommandProcessor>();
{
processors.put('>', new PointerToRightProcessor());
processors.put('<', new PointerToLeftProcessor());
processors.put('+', new IncrementProcessor());
processors.put('-', new DecrementProcessor());
processors.put('.', new WriteProcessor());
processors.put(',', new ReadProcessor());
processors.put('[', new ForwardProcessor());
processors.put(']', new BackProcessor());
}
private final String src;
private List<Integer> tape;
private int pointer;
private int current;
/**
* @param src
*/
public Brainfuck(String src) {
this.src = src;
}
/**
*
*/
public synchronized void run() {
tape = new ArrayList<Integer>();
tape.add(0);
for(pointer = 0, current = 0; current < src.length(); current++) {
char c = src.charAt(current);
CommandProcessor processor = processors.get(c);
if(processor != null) {
processor.process();
}
}
}
/**
* @param args
* @throws IOException
*/
public static void main(String... args) throws IOException {
new Brainfuck(Argf.read(args)).run();
}
private class PointerToRightProcessor implements CommandProcessor {
@Override
public void process() {
pointer++;
while(tape.size() <= pointer) {
tape.add(0);
}
}
}
private class PointerToLeftProcessor implements CommandProcessor {
@Override
public void process() {
pointer--;
if(pointer < 0) {
throw new IndexOutOfBoundsException();
}
}
}
private class IncrementProcessor implements CommandProcessor {
@Override
public void process() {
tape.set(pointer, tape.get(pointer) + 1);
}
}
private class DecrementProcessor implements CommandProcessor {
@Override
public void process() {
tape.set(pointer, tape.get(pointer) - 1);
}
}
private class WriteProcessor implements CommandProcessor {
@Override
public void process() {
System.out.print((char)tape.get(pointer).intValue());
}
}
private class ReadProcessor implements CommandProcessor {
@Override
public void process() {
try {
tape.set(pointer, System.in.read());
}
catch(IOException e) {
}
}
}
private class ForwardProcessor implements CommandProcessor {
@Override
public void process() {
if(tape.get(pointer) == 0) {
current++;
for(int brackets = 1; brackets > 0; current++) {
char c = src.charAt(current);
if(c == '[') {
brackets++;
}
else if(c == ']') {
brackets--;
}
}
current--;
}
}
}
private class BackProcessor implements CommandProcessor {
@Override
public void process() {
if(tape.get(pointer) != 0) {
current--;
for(int brackets = 1; brackets > 0; current--) {
char c = src.charAt(current);
if(c == '[') {
brackets--;
}
else if(c == ']') {
brackets++;
}
}
current++;
}
}
}
}
Hello World
入力ファイルとして以下のファイルを準備します。++++++++[>+++++++++<-]>.
<+++++[>++++++<-]>-.
<++[>+++<-]>+.
.
+++.
<++++++[>----<-]>.
<++++++[>++++<-]>.
+++.
------.
--------.
もしくは
+++++++++[>++++++++>+++++++++++>+++++<<<-]>.>++.+++++++..+++.>-.
------------.<++++++++.--------.+++.------.--------.>+.
helloworld.txt
として保存して、$ java st.happy_camper.esoteric.brainfuck.Brainfuck helloworld.txt
HelloWorld
Argf
クラス
引数で渡されたファイルをすべて読み込む部分をユーティリティとして切り出しています。引数がなければ標準入力から読み込みます。
package st.happy_camper.esoteric.util;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
/**
* @author ueshin
*/
public class Argf {
/**
*
*/
private Argf() {
}
/**
* @param args
* @return
* @throws IOException
*/
public static String read(String... args) throws IOException {
StringBuilder src = new StringBuilder();
if(args != null && args.length > 0) {
for(String file : args) {
read(src, new FileReader(file));
}
}
else {
read(src, new InputStreamReader(System.in));
}
return src.toString();
}
/**
* @param src
* @param reader
* @throws IOException
*/
private static void read(StringBuilder src, Reader reader)
throws IOException {
BufferedReader BufferedReader = null;
try {
BufferedReader = new BufferedReader(reader);
String line;
while((line = BufferedReader.readLine()) != null) {
src.append(line);
src.append("\n");
}
}
finally {
if(BufferedReader != null) {
BufferedReader.close();
BufferedReader = null;
}
}
}
}