challenge BFコンパイラー

「どう書く?」でまだ出ていないのが不思議なお題。それがBF処理系。 ここでは、BFで書かれたソースを、同じ言語に変換するコンパイラーを募集します。

私自身、すでにPerlとJavaScriptに関しては http://blog.livedoor.jp/dankogai/archives/50545151.html でやっているのですが、他の言語バージョンも是非見たいので。

Dan the Brainf.ucker

以下のようにonelinerで可能です。 ただしLanguage::BF 0.03が必要です。 CodeRepos経由 で、

  • svn co svn.coderepos.org/share/lang/perl/Language-BF
  • cd Language-BF/trunk
  • perl Makefile.PL
  • make install

するか、CPANにVersion 0.03が現れるのをお待ち下さい。

Dan the Brainf.cker

1
2
3
4
perl -MLanguage::BF \
  -e 'print Language::BF->new_from_file(shift)->as_perl' t/hello.bf \
  | perl
Hello World!

Posted feedbacks - Flatten

Nested Hidden
ひさびさの一番かな?

入力を文字列にしたい場合なんかは
Console.withInを使うとできます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import scala.io.Source.fromFile

object BF{
  def main(args:Array[String]) = {
    if(args.size == 0) print("scala BF [sourcecode filename]")
    else
    args.foreach{f=> (new Machine).interpret(fromFile(f).mkString("")) }
  }
}

object Machine {
  final val MEM = 0xffffff
  final val VAL = 0xff
}

class Machine{
  var _p = 0
  val _mem = new Array[int](Machine.MEM)
  def p_=(v:int) = _p = v&Machine.MEM
  def p = _p
  def mem_=(v:int) = _mem(p)=v&Machine.VAL
  def mem = _mem(p)

  def abort = error("Missing corresponding parenthesis.")

  def interpret(code:String):unit = interpret(code.toArray)
  def interpret(code:Array[char]):unit = {
    val (s,m) = ((List[int](), List[(int,int)]()) /: code.zipWithIndex){
      (r,c) => c._1 match {
        case '[' => (c._2::r._1, r._2)
        case ']' => r._1 match {
          case x::xs => (xs, (x,c._2)::(c._2,x)::r._2)
          case _ => abort
        }
        case _   => r
    }}
    if(s.size > 0) abort
    val parenMap = Map(m:_*)

    var i = -1;while({i=i+1;i<code.size}) code(i) match {
      case '>' => p = p+1
      case '<' => p = p-1
      case '+' => mem = mem+1
      case '-' => mem = mem-1
      case '.' => print(mem.asInstanceOf[char])
      case ',' => mem = readChar.asInstanceOf[int]
      case '[' if mem == 0 => i = parenMap(i)
      case ']' if mem != 0 => i = parenMap(i)
      case _   => ()
    }
  }
}

お題を見て「BF→言語Aへのトランスレータを言語Aで製作する」のだと思ったんですけど、お手本見るとなんかちょっと違う感じがしたので全部やってみました (^-^;; BF ソースファイルから C# ソースと実行ファイルを生成してそれを実行します。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Microsoft.CSharp;
static class BFCompiler {
    public static void Main(String[] args) {
        if (0 < args.Length && File.Exists(args[0])) {
            using(StringWriter sw = new StringWriter())
            using(StreamReader sr = new StreamReader(args[0])) {
                Stack<string> labels = new Stack<string>();
                int label_num = 0;
                sw.WriteLine("using System; static class BF {");
                sw.WriteLine("public static void Main() {");
                sw.WriteLine("byte[] m = new byte[256]; int p = 0;");
                foreach(char c in sr.ReadToEnd()) {
                    switch(c) {
                        case '+': sw.WriteLine("m[p]++;"); break;
                        case '-': sw.WriteLine("m[p]--;"); break;
                        case '>': sw.WriteLine("p++;"); break;
                        case '<': sw.WriteLine("p--;"); break;
                        case '.': sw.WriteLine("Console.Write((char)m[p]);"); break;
                        case ',': sw.WriteLine("m[p] = (byte)Console.Read();"); break;
                        case '[': {
                            string ll = "L" + (label_num++);
                            labels.Push(ll);
                            sw.WriteLine("if (m[p] == 0) goto {0}_END;", ll);
                            sw.WriteLine("{0}_START:;", ll);
                            break;
                        }
                        case ']': {
                            string ll = labels.Pop();
                            sw.WriteLine("if (m[p] != 0) goto {0}_START;", ll);
                            sw.WriteLine("{0}_END:;", ll);
                            break;
                        }
                    }
                }
                sw.WriteLine("}}");
                Generate(Path.GetFileNameWithoutExtension(args[0]), sw.ToString());
            }
        }
        else {
            Console.WriteLine("usage: bfc [sourcefile]");
        }
    }
    private static void Generate(string filename, string cs_code) {
        // ソースファイル生成
        using (StreamWriter sw = new StreamWriter(filename + ".cs")) {
            sw.Write(cs_code);
        }
        // 実行ファイル生成
        CompilerParameters param = new CompilerParameters();
        param.OutputAssembly = filename + ".exe";
        param.GenerateExecutable = true;
        CompilerResults rs = new CSharpCodeProvider().CompileAssemblyFromSource(param, cs_code);
        // 実行
        rs.CompiledAssembly.GetType("BF").GetMethod("Main").Invoke(null, null);
    }
}

以下のようにonelinerで可能です。 ただしLanguage::BF 0.03が必要です。 CodeRepos経由 で、

  • svn co svn.coderepos.org/share/lang/perl/Language-BF
  • cd Language-BF/trunk
  • perl Makefile.PL
  • make install

するか、CPANにVersion 0.03が現れるのをお待ち下さい。

Dan the Brainf.cker

1
2
3
4
perl -MLanguage::BF \
  -e 'print Language::BF->new_from_file(shift)->as_perl' t/hello.bf \
  | perl
Hello World!

>お題を見て「BF→言語Aへのトランスレータを言語Aで製作する」のだと思ったんですけど

それであっていると思いますよ。お手本は 「Language::BF->new_from_file(shift)->as_perl」というコードで「t/hello.bf」というBrainf*ckで書かれたコードを読んでPerlに変換し、その出力をさいごの「| perl」でもう一度Perlに食わせて実行させているわけです。

perl -MLanguage::BF \
-e 'print Language::BF->new_from_file(shift)->as_perl' t/hello.bf \
| perl
Hello World!

SiroKuroさんがおっしゃっているお手本は
http://blog.livedoor.jp/dankogai/archives/50545151.html  

のほうだと思います。投稿時間を見てみるとわかります。

僕もURLの方だけみて勘違いしてました・・・
今から書き直します(^^;

ひねり無し。
python bf.py -o hello.py hello.bf  みたいな感じで使います。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import sys
from getopt import getopt

def encode(bfcode):
    depth = code = 0
    pycode = []
    stack = []
    
    f = file(default['-o'], 'w')
    
    pycode.append('import sys')
    pycode.append('tape, ptr, code = {}, 0, 0')
    
    while code != len(bfcode):
        c = bfcode[code]
        
        if c == '>':
            pycode.append('\t' * depth + 'ptr += 1')
        elif c == '<':
            pycode.append('\t' * depth + 'ptr -= 1')
        elif c == '+':
            pycode.append('\t' * depth + 'tape[ptr] = tape.get(ptr, 0) + 1')
        elif c == '-':
            pycode.append('\t' * depth + 'tape[ptr] = tape.get(ptr, 0) - 1')
        elif c == ',':
            pycode.append('\t' * depth + 'tape[ptr] = sys.stdin.read(1)')
        elif c == '.':
            pycode.append('\t' * depth + 'sys.stdout.write(chr(tape.get(ptr, 0)))')
        elif c == '[':
            pycode.append('\t' * depth + 'while tape.get(ptr, 0):')
            stack.append(depth)
            depth += 1
        elif c == ']':
            depth = stack.pop()
        
        code += 1
    
    file(default['-o'], 'w').write('\n'.join(pycode))


if __name__ == '__main__':
    default = {'-o': 'a.py'}
    optlist, args = getopt(sys.argv[1:], 'o:', [])
    if args:
        default.update(optlist)
        encode(''.join(file(args[0]).readlines()))

簡単の為、標準出力を使用。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>
#include <stdlib.h>

int main(int arg_c, char **arg_v) {
    FILE *fp;
    int c;

    if (*++arg_v == NULL) {
        fprintf(stderr, "usage: bfc [source file]\n");
        exit(EXIT_FAILURE);
    }
    if ((fp = fopen(*arg_v, "r")) == NULL) {
        fprintf(stderr, "file open error.\n");
        exit(EXIT_FAILURE);
    }
    
    puts("#include <stdio.h>");
    puts("#include <stdlib.h>");
    puts("int main(void) {");
    puts(" char *s, *p;");
    puts(" p = s = (char *)calloc(1024, 1);");
    
    while ((c = getc(fp)) != EOF) {
        switch (c) {
            case '>': puts(" ++p;"); break;
            case '<': puts(" --p;"); break;
            case '+': puts(" ++*p;"); break;
            case '-': puts(" --*p;"); break;
            case '.': puts(" putchar(*p);"); break;
            case ',': puts(" *p = getchar();"); break;
            case '[': puts(" while (*p) {"); break;
            case ']': puts(" }"); break;
            default: break;
        }
    }
    
    puts(" free(s);");
    puts("}");
    
    fclose(fp);
}

あ、ごめんなさい。yuin さんの仰るとおり、弾さんのブログを見ての疑問でした。弾さんのブログだと普通に実行してるみたいでしたので……。 んで、トランスレートかな?コンパイルかな?実行かな?と迷ったので全部やってみた感じです。どれか1つはあってると思いたい (^-^;;

出力のインデント処理と配列の長さを引数で調整できるようにしてみました。
./bf2c hello.bf > hello.c
または
./bf2c hello.bf 128 > hello.c
のように使います。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <stdio.h>
#include <stdlib.h>

char *code[] = { "sp++;", "sp--;", "(*sp)++;", "(*sp)--;", "putchar(*sp);",
                 "*sp = getchar();", "while(*sp){", "}" };

int main( int argc, char *argv[] ){
   int c,i,j,n=0;
   int indent = 1;
   int length;
   FILE *fp;

   if( argc < 2 ){
      fprintf(stderr,"Usage: %s sourcefile\n", argv[0] );
      return EXIT_FAILURE;
   }
   if( (fp = fopen( argv[1], "r" )) == NULL ){
      fprintf(stderr,"Error: %s cannot opened\n", argv[1] );
      return EXIT_FAILURE;
   }
   if( !argv[2] || (length = atoi( argv[2] )) <= 0 ){
      length = 256;
   }

   puts("#include <stdio.h>");
   puts("#include <stdlib.h>");
   printf("#define DATA_LEN %d\n", length);
   puts("char code[DATA_LEN];");
   puts("int main (void){");
   puts("   char *sp = code;");
   while( (c=fgetc( fp )) != EOF ){
      switch(c){
      case '>': i = 0; break;
      case '<': i = 1; break;
      case '+': i = 2; break;
      case '-': i = 3; break;
      case '.': i = 4; break;
      case ',': i = 5; break;
      case '[': i = 6; indent++; break;
      case ']': i = 7; indent--; break;
      default:
         /* skip other characters */
         continue;
      }
      for( j = 0; j < (indent+(i==6?-1:0)); j++ ){
         printf("   ");
      }
      printf("%s\n", code[i] );
   }
   puts("   return EXIT_SUCCESS;");
   puts("}");
   fclose(fp);
   return EXIT_SUCCESS;
}

コードをラベルと命令の列みたいなのにして、
prog の中へ放り込みます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(defun compile-bf (str &optional (size 30000) (offset 0))
  (let ((tags ()))
    (flet ((translate (c)
             (case c
               (#\> '((incf ptr)))
               (#\< '((decf ptr)))
               (#\+ '((setf *ptr (logand (1+ *ptr) #xff))))
               (#\- '((setf *ptr (logand (1- *ptr) #xff))))
               (#\. '((write-char (code-char *ptr))))
               (#\, '((setf *ptr (char-code (read-char)))))
               (#\[ (let ((t1 (gensym)) (t2 (gensym)))
                      (setf tags (list* t1 t2 tags))
                      `(,t1 (if (= *ptr 0) (go ,t2)))))
               (#\] (let ((t1 (pop tags)) (t2 (pop tags)))
                      `((if (/= *ptr 0) (go ,t1)) ,t2))))))
      `(symbol-macrolet ((*ptr (aref array ptr)))
         (prog ((array ,(make-array size :initial-element 0))
                (ptr ,offset))
           ,@(loop for c across str append (translate c)))))))

;;; test
(eval (compile-bf "+++++++++[>++++++++>+++++++++++>+++++<<<-]>.>++.
+++++++..+++.>-.------------.<++++++++.--------.+++.------.--------.>+."))

はっきりしなくてごめんなさい。にしおさんが言う通り。

コンパイラー(トランスレーター)
  BF ====> 言語A
      言語A

というのが本来の趣旨で、そしてBFの場合こちらの方が

インタープリター(ランタイム)
  BF => 言語Aで書かれた実行環境

よりもずっと実装が簡単なので。
ちなみにLanguage::BFはどちらの機能も持っています。

Dan the Brainf.cker

題意を読み間違えてたので。

こっちのほうがかなり楽です。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import scala.io.Source.fromFile

object BFC{
  def main(args:Array[String]) = {
    if(args.size == 0){
      println("Usage:scala BFC [sourcecode filename]")
    }else{
      println("""
        object Machine {
          final val MEM = 0xffffff
          final val VAL = 0xff
        }

        class Machine{
          var _p = 0
          val _mem = new Array[int](Machine.MEM)
          def p_=(v:int) = _p = v&Machine.MEM
          def p = _p
          def mem_=(v:int) = _mem(p)=v&Machine.VAL
          def mem = _mem(p)

          def eval = {
      """)
      val code = fromFile(args(0)).mkString("").toList
      if(code.count('['==_) != code.count(']'==_)) {
        error("Missing corresponding parenthesis.")
      }
      code.foreach(c=>println(c match {
        case '>' => "p = p+1"
        case '<' => "p = p-1"
        case '+' => "mem = mem+1"
        case '-' => "mem = mem-1"
        case '.' => "print(mem.asInstanceOf[char])"
        case ',' => "mem = readChar.asInstanceOf[int]"
        case '[' => "while(mem != 0){"
        case ']' => "}"
        case _   => ""
      }))
      println("}};(new Machine).eval;")
    }
  }
}

ケースが嫌いな私は、以下のように書き換えてしまいました。機能的には#3955と互換ですが、出力されたCコードのコンパイルには差し支えないのでインデントは省略しました。

Dan the Brainf.cker

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>
#include <stdlib.h>

int main( int argc, char *argv[] ){
    int c;
    char *code[256];
    int length;
    FILE *fp;

    if( argc < 2 ){
        fprintf(stderr,"Usage: %s sourcefile\n", argv[0] );
        return EXIT_FAILURE;
    }
    if( (fp = fopen( argv[1], "r" )) == NULL ){
        fprintf(stderr,"Error: %s cannot opened\n", argv[1] );
        return EXIT_FAILURE;
    }
    if( !argv[2] || (length = atoi( argv[2] )) <= 0 ){
        length = 256;
    }

    for (c = 0; c < 256; c++) code[c] = NULL;
    code['>'] = "sp++;";
    code['<'] = "sp--;";
    code['+'] = "(*sp)++;";
    code['-'] = "(*sp)--;";
    code['.'] = "putchar(*sp);";
    code[','] = "*sp = getchar();";
    code['['] = "while(*sp){";
    code[']'] = "}";

    puts("#include <stdio.h>");
    puts("#include <stdlib.h>");
    printf("#define DATA_LEN %d\n", length);
    puts("char code[DATA_LEN];");
    puts("int main (void){");
    puts("   char *sp = code;");
    while( (c=fgetc( fp )) != EOF ) if (code[c]) printf("%s\n", code[c]); 
    puts("   return EXIT_SUCCESS;");
    puts("}");
    fclose(fp);
    return EXIT_SUCCESS;
}

ひねりなし。
配列の値がbyte型を越える場合についてはとりあえず考慮しない方向で…
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class BF
  def compile(str)
    depth = 0
    code = []
    code << "mem = [0]"
    code << "ptr = 0"
    str.each_byte do |ch|   
      if ch == ?>
        code << "\t" * depth + "ptr += 1; mem[ptr] = 0 if ptr >= mem.size"
      elsif ch == ?<
        code << "\t" * depth + "(ptr == 0)? mem.unshift(0) : ptr -= 1"
      elsif ch == ?+
        code << "\t" * depth + "mem[ptr] += 1"
      elsif ch == ?-
        code << "\t" * depth + "mem[ptr] -= 1"
      elsif ch == ?.
        code << "\t" * depth + "putc(mem[ptr])"
      elsif ch == ?,
        code << "\t" * depth + "mem[ptr] = STDIN.getc"
      elsif ch == ?[
        code << "\t" * depth + "while(mem[ptr] != 0) do"
        depth += 1
      elsif ch == ?]
        depth -= 1
        code << "\t" * depth + "end"
      end
    end
   code.join "\n"
  end
end

eval BF.new.compile("++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.") # => Hello World!

自己ツッコミ。
このままだと"~[]~" のようなBFコードがあった際にwhile文のところで文法エラーを起こすので、
']'が出た時にはpassをダミーで放り込んだ方がよさそうです。


あと、最初間違えてインタプリタを書いてしまった名残で変なやり方になっていました。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
def encode(bfcode):
-   depth = code = 0 
+   depth = 0
    pycode = []
    stack = []
    
    f = file(default['-o'], 'w')
    
    pycode.append('import sys')
-   pycode.append('tape, ptr, code = {}, 0, 0')
+   pycode.append('tape, ptr = {}, 0')
    

-     while code != len(bfcode):
-        c = bfcode[code]
+     for c in bfcode:
        if c == '>':
            pycode.append('\t' * depth + 'ptr += 1')
        elif c == '<':
            pycode.append('\t' * depth + 'ptr -= 1')
        elif c == '+':
            pycode.append('\t' * depth + 'tape[ptr] = tape.get(ptr, 0) + 1')
        elif c == '-':
            pycode.append('\t' * depth + 'tape[ptr] = tape.get(ptr, 0) - 1')
        elif c == ',':
            pycode.append('\t' * depth + 'tape[ptr] = sys.stdin.read(1)')
        elif c == '.':
            pycode.append('\t' * depth + 'sys.stdout.write(chr(tape.get(ptr, 0)))')
        elif c == '[':
            pycode.append('\t' * depth + 'while tape.get(ptr, 0):')
            stack.append(depth)
            depth += 1
        elif c == ']':
+           pycode.append('\t' * depth + 'pass')
            depth = stack.pop()

-    code += 1

久しぶりに投稿します。
-v optimize=1 とするとオプティマイズされます(笑

helloworld.bf:
++++++++++[>+++++++>++++++++++>+++>+<<<<-]
>++.>+.+++++++..+++.>++.<<+++++++++++++++.
>.+++.------.--------.>+.>.

% awk -f bf2awk.awk helloworld.bf > helloworld.awk
% awk -f helloworld.awk 
Hello World!

helloworld.awk:
BEGIN {
  ix = 0
  st[ix]++
  st[ix]++
  st[ix]++
  st[ix]++
  st[ix]++
  st[ix]++
  st[ix]++
  st[ix]++
  st[ix]++
  st[ix]++
  while (st[ix]) {
    ix++
    st[ix]++
    st[ix]++
    st[ix]++
    st[ix]++
    st[ix]++
    st[ix]++
    st[ix]++
    ix++
    st[ix]++
    st[ix]++
    st[ix]++
    st[ix]++
    st[ix]++
    st[ix]++
    st[ix]++
    st[ix]++
    st[ix]++
    st[ix]++
    ix++
    st[ix]++
    st[ix]++
    st[ix]++
    ix++
    st[ix]++
    ix--
    ix--
    ix--
    ix--
    st[ix]--
  }
  ix++
  st[ix]++
  st[ix]++
  printf("%c", st[ix])
  ix++
  st[ix]++
  printf("%c", st[ix])
  st[ix]++
  st[ix]++
  st[ix]++
  st[ix]++
  st[ix]++
  st[ix]++
  st[ix]++
  printf("%c", st[ix])
  printf("%c", st[ix])
  st[ix]++
  st[ix]++
  st[ix]++
  printf("%c", st[ix])
  ix++
  st[ix]++
  st[ix]++
  printf("%c", st[ix])
  ix--
  ix--
  st[ix]++
  st[ix]++
  st[ix]++
  st[ix]++
  st[ix]++
  st[ix]++
  st[ix]++
  st[ix]++
  st[ix]++
  st[ix]++
  st[ix]++
  st[ix]++
  st[ix]++
  st[ix]++
  st[ix]++
  printf("%c", st[ix])
  ix++
  printf("%c", st[ix])
  st[ix]++
  st[ix]++
  st[ix]++
  printf("%c", st[ix])
  st[ix]--
  st[ix]--
  st[ix]--
  st[ix]--
  st[ix]--
  st[ix]--
  printf("%c", st[ix])
  st[ix]--
  st[ix]--
  st[ix]--
  st[ix]--
  st[ix]--
  st[ix]--
  st[ix]--
  st[ix]--
  printf("%c", st[ix])
  ix++
  st[ix]++
  printf("%c", st[ix])
  ix++
  printf("%c", st[ix])
}

% awk -v optimize=1 -f bf2awk.awk helloworld.bf > helloworld-optimized.awk
% awk -f helloworld-optimized.awk 
Hello World!

helloworld-optimized.awk:
BEGIN {
  ix = 0
  st[ix] += 10
  while (st[ix]) {
    st[++ix]++
    st[ix] += 6
    st[++ix]++
    st[ix] += 9
    st[++ix]++
    st[ix] += 2
    st[++ix]++
    ix -= 4
    st[ix]--
  }
  st[++ix]++
  st[ix]++
  printf("%c", st[ix])
  st[++ix]++
  printf("%c", st[ix])
  st[ix] += 7
  printf("%c", st[ix])
  printf("%c", st[ix])
  st[ix] += 3
  printf("%c", st[ix])
  st[++ix]++
  st[ix]++
  printf("%c", st[ix])
  ix -= 2
  st[ix] += 15
  printf("%c", st[ix])
  printf("%c", st[++ix])
  st[ix] += 3
  printf("%c", st[ix])
  st[ix] -= 6
  printf("%c", st[ix])
  st[ix] -= 8
  printf("%c", st[ix])
  st[++ix]++
  printf("%c", st[ix])
  printf("%c", st[++ix])
}
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
BEGIN {
    read_buf = ""
    if (optimize) reset_last()

    indent = ""
    indent_print("BEGIN {")

    indent_incr()
    indent_print("ix = 0")
}
{
    gsub(/#.*$/,""); # comment
    gsub(/[^\[\]<>+\-.,]/,""); if (/^$/) next

    N = split($0, op, "")
    for (i=1; i<=N; i++) {
        if (op[i] == ">") {
            if (optimize) {
                if (last_inst == "ix") {
                    last_arg++
                } else {
                    out_last()
                    set_last("ix", 1)
                }
            } else {
                indent_print("ix++")
            }
        } else if (op[i] == "<") {
            if (optimize) {
                if (last_inst == "ix") {
                    last_arg--
                } else {
                    out_last()
                    set_last("ix", -1)
                }
            } else {
                indent_print("ix--")
            }
        } else if (op[i] == "+") {
            if (optimize) {
                if (last_inst == "st[ix]") {
                    last_arg++
                } else {
                    s = set_incr_decr("st[ix]")
                    if (last_inst) out_last()
                    set_last(s, 1)
                }
            } else {
                indent_print("st[ix]++")
            }
        } else if (op[i] == "-") {
            if (optimize) {
                if (last_inst == "st[ix]") {
                    last_arg--
                } else {
                    s = set_incr_decr("st[ix]")
                    if (last_inst) out_last()
                    set_last(s, -1)
                }
            } else {
                indent_print("st[ix]--")
            }
        } else if (op[i] == ".") {
            if (optimize) {
                s = set_incr_decr("printf(\"%c\", st[ix])")
                if (last_inst) out_last()
                indent_print(s)
            } else {
                indent_print("printf(\"%c\", st[ix])")
            }
        } else if (op[i] == ",") {
            if (optimize) {
                s = set_incr_decr("st[ix] = getchar()")
                if (last_inst) out_last()
                indent_print(s)
            } else {
                indent_print("st[ix] = getchar()")
            }
        } else if (op[i] == "[") {
            if (optimize) {
                s = set_incr_decr("while (st[ix]) {")
                if (last_inst) out_last()
                indent_print(s)
                indent_incr()
            } else {
                indent_print("while (st[ix]) {")
                indent_incr()
            }
        } else if (op[i] == "]") {
            if (optimize) out_last()
            indent_decr()
            indent_print("}")
        } else {
            ;
        }
    }
}
END {
    if (optimize) out_last()
    indent_decr()
    indent_print("}")
}

function getchar(  ch)
{
    if (read_buf ~ /^$/) getline read_buf
    ch = substr(read_buf,1,1)
    read_buf = substr(read_buf,2)
    return ch
}
function indent_incr()
{
    indent = "  " indent
}
function indent_decr()
{
    indent = substr(indent, 3)
}
function indent_print(line)
{
    print indent line
}
function set_last(inst,arg)
{
    last_inst = inst
    last_arg = arg
}
function reset_last()
{
    last_inst = ""
    last_arg = 0
}
function out_last(  diff)
{
    if (last_inst ~ /ix/) {
        if (last_arg > 1)
            diff = " += " last_arg
        else if (last_arg == 1)
            diff = "++"
        else if (last_arg == 0)
            diff = ""
        else if (last_arg == -1)
            diff = "--"
        else if (last_arg < -1)
            diff = " -= " (0 - last_arg)

        if (diff) indent_print(last_inst diff)
    }
    last_inst = ""
}
function set_incr_decr(s)
{
    if (last_inst == "ix") {
        if (last_arg == 1) { gsub(/ix/, "++ix", s); last_inst = "" }
        else if (last_arg == -1) { gsub(/ix/, "--ix", s); last_inst = "" }
    }
    return s
}

これまたif文の羅列を置き換える方向で書き換えてみました。
Cと違ってHashが気軽に使えるのがうれしい。
next unless が使えるのもPerl Mongerとしてはうれしい。
    str.unpack("C*").map{|c| c.chr}.each do |ch|
は、単に{}とdo endを両方使ってみたかったから。
Dan the Occasional Rubyist
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class BF
  @@opcode = {
    '>' => "ptr += 1; mem[ptr] = 0 if ptr >= mem.size",
    '<' => "(ptr == 0)? mem.unshift(0) : ptr -= 1",
    '+' => "mem[ptr] += 1",
    '-' => "mem[ptr] -= 1",
    '.' => "putc(mem[ptr])",
    ',' => "mem[ptr] = STDIN.getc",
    '[' => "while(mem[ptr] != 0) do",
    ']' => "end"
  }
  def compile(str)
    code = ["mem = [0]", "ptr = 0"]
    str.unpack("C*").map{|c| c.chr}.each do |ch|
      next unless @@opcode[ch]
      code << @@opcode[ch]
    end
   code.join "\n"
  end
end

Squeak Smalltalk で。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
| bf st op in |
bf := FileStream fileNamed: 'hello.bf'.
in := FileStream fileNamed: 'in.txt'.
in binary.
st := WriteStream with: '| ss | ss := ReadWriteStream with: #(0). ss reset'.
[(op := bf next) notNil] whileTrue: [
    st nextPutAll: (op caseOf: {
        [$>] -> ['. ss next. ss atEnd ifTrue: [ss nextPut: 0; back]'].
        [$<] -> ['. ss back'].
        [$+] -> ['. ss nextPut: ss peek + 1; back'].
        [$-] -> ['. ss nextPut: ss peek - 1; back'].
        [$.] -> ['. Transcript show: (ss peek asCharacter)'].
        [$,] -> ['. ss nextPut: ', in next printString,' value; back'].
        [$[] -> ['. [ss peek isZero] whileFalse: ['].
        [$]] -> [']']} otherwise: [''])].
bf close.  in close.
World findATranscript: nil.
Compiler evaluate: st contents

おぉ、なるほど。勉強になります。

a2p的にもきれいなawkコードを吐きますねwww

Dan the Cyberpolyglot

1
2
% awk -f bfcc.awk helloworld.bf | a2p | perl
Hello, World!

optimize=1 の時に while(st[ix]) の直前に ix++ や ix-- が来ると while(st[++ix]) のように誤ったオプティマイズが行われるバグを修正
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
--- bf2awk.awk.orig    2007-11-14 00:43:07.000000000 +0900
+++ bf2awk.awk    2007-11-14 01:46:46.000000000 +0900
@@ -77,15 +77,9 @@
                 indent_print("st[ix] = getchar()")
             }
         } else if (op[i] == "[") {
-            if (optimize) {
-                s = set_incr_decr("while (st[ix]) {")
-                if (last_inst) out_last()
-                indent_print(s)
-                indent_incr()
-            } else {
-                indent_print("while (st[ix]) {")
-                indent_incr()
-            }
+            if (optimize) out_last()
+            indent_print("while (st[ix]) {")
+            indent_incr()
         } else if (op[i] == "]") {
             if (optimize) out_last()
             indent_decr()

オプティマイズ時に
++ix
st[ix]++
st[ix]++
st[ix]++
が
st[++ix]++
st[ix] += 2
となっていたのを
st[++ix] += 3
のようにちゃんとまとめるように修正するパッチ
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
--- bf2awk.awk.orig    2007-11-14 00:43:07.000000000 +0900
+++ bf2awk.awk    2007-11-14 01:55:50.000000000 +0900
@@ -38,7 +38,7 @@
             }
         } else if (op[i] == "+") {
             if (optimize) {
-                if (last_inst == "st[ix]") {
+                if (last_inst ~ /^st\[/) {
                     last_arg++
                 } else {
                     s = set_incr_decr("st[ix]")
@@ -50,7 +50,7 @@
             }
         } else if (op[i] == "-") {
             if (optimize) {
-                if (last_inst == "st[ix]") {
+                if (last_inst ~ /^st\[/) {
                     last_arg--
                 } else {
                     s = set_incr_decr("st[ix]")

BFによるBFコンパイラーは、これほど簡単です:-)

種明かしは、こちら

本来であれば、,[.,]でもOKなのですが、これだとEOFが処理できません。

Dan the Brainf.cker

1
,+[-.,+]

これまたifの羅列をHashに置き換え。ただし、pythonの場合、[]は少し特別扱いが必要。こちらはindent不要とは行かないので。 あと、関数名やインターフェースも好みにあわせて変えました。

Dan the Novice Snake Tamer

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/usr/bin/env python
import sys
from getopt import getopt

def bf2py(bfcode):
    depth = 0
    opcode = {
        '>':'ptr += 1',
        '<':'ptr -= 1',
        '+':'tape[ptr] = tape.get(ptr, 0) + 1',
        '-':'tape[ptr] = tape.get(ptr, 0) - 1',
        ',':'tape[ptr] = sys.stdin.read(1)',
        '.':'sys.stdout.write(chr(tape.get(ptr, 0)))',
        '[':'while tape.get(ptr, 0):',
        ']':'pass'
    }
    pycode = []
    stack = []
    
    pycode.append('import sys')
    pycode.append('tape, ptr = {}, 0')
    
    for c in bfcode:
        if opcode.has_key(c):
            pycode.append( '\t' * depth + opcode[c])
            if c == '[':
                stack.append(depth)
                depth += 1
            elif c == ']':
                depth = stack.pop()
     
    return '\n'.join(pycode)

if __name__ == '__main__':
    defout = 'a.py'
    optlist, args = getopt(sys.argv[1:], 'o:', [])
    if args:
        pysrc = bf2py(''.join(file(args[0]).readlines()));
        file(defout or ops['-o'], 'w').write(pysrc)

うう駄目だ、getchar() の定義が出力されないので、#3976の ,+[-.,+] をコンパイルしても動かない。
しかも getchar() が文字コードを返すようになってない。ord() を実装。
全面書き換え。

a2pをかましても動くように getline, index 回りを微調整。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
BEGIN {
    if (optimize) reset_last()

    indent = ""
    print "BEGIN {"

    indent_incr()
    indent_print("read_buf = \"\"")
    indent_print("ix = 0")
}
{
    gsub(/#.*$/,""); # comment
    gsub(/[^\[\]<>+\-.,]/,""); if (/^$/) next

    N = split($0, op, "")
    for (i=1; i<=N; i++) {
        if (op[i] == ">") {
            if (optimize) {
                if (last_inst == "ix") {
                    last_arg++
                } else {
                    out_last()
                    set_last("ix", 1)
                }
            } else {
                indent_print("ix++")
            }
        } else if (op[i] == "<") {
            if (optimize) {
                if (last_inst == "ix") {
                    last_arg--
                } else {
                    out_last()
                    set_last("ix", -1)
                }
            } else {
                indent_print("ix--")
            }
        } else if (op[i] == "+") {
            if (optimize) {
                if (last_inst ~ /^st\[/) {
                    last_arg++
                } else {
                    s = set_incr_decr("st[ix]")
                    if (last_inst) out_last()
                    set_last(s, 1)
                }
            } else {
                indent_print("st[ix]++")
            }
        } else if (op[i] == "-") {
            if (optimize) {
                if (last_inst ~ /^st\[/) {
                    last_arg--
                } else {
                    s = set_incr_decr("st[ix]")
                    if (last_inst) out_last()
                    set_last(s, -1)
                }
            } else {
                indent_print("st[ix]--")
            }
        } else if (op[i] == ".") {
            if (optimize) {
                s = set_incr_decr("printf(\"%c\", st[ix])")
                if (last_inst) out_last()
                indent_print(s)
            } else {
                indent_print("printf(\"%c\", st[ix])")
            }
        } else if (op[i] == ",") {
            if (optimize) {
                s = set_incr_decr("st[ix] = getchar()")
                if (last_inst) out_last()
                indent_print(s)
            } else {
                indent_print("st[ix] = getchar()")
            }
        } else if (op[i] == "[") {
            if (optimize) {
                s = set_incr_decr("while (st[ix]) {")
                if (last_inst) out_last()
                indent_print(s)
                indent_incr()
            } else {
                indent_print("while (st[ix]) {")
                indent_incr()
            }
        } else if (op[i] == "]") {
            if (optimize) out_last()
            indent_decr()
            indent_print("}")
        } else {
            ;
        }
    }
}
END {
    if (optimize) out_last()
    indent_decr()
    indent_print("}")

    print "function getchar(  ch)"
    print "{"
    print "  if (read_buf ~ /^$/) {"
    print "    if (eof) return -1"
#    print "    if ((getline read_buf) < 1) { eof = 1; return -1 }"
    print "    if (getline != 1) { eof = 1; return -1 }" # a2p
    print "    read_buf = $0" # a2p
    print "  }"
    print "  ch = ord(substr(read_buf,1,1))"
    print "  read_buf = substr(read_buf,2)"
    print "  return ch"
    print "}"

    print "function ord(ch, ofs)"
    print "{"
    print "  if (ch == \"\\t\") return 9"
    print "  if (ch == \"\\n\") return 13"
#    print "  ofs = index(\" !\\\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\", ch)"
    print "  if (ch == \"@\") return 64" # a2p
    print "  ofs = index(\" !\\\"#$%&'()*+,-./0123456789:;<=>?.ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\", ch)" # a2p
    print "  return ofs ? 31 + ofs : 0"
    print "}"
}

function indent_incr()
{
    indent = "  " indent
}
function indent_decr()
{
    indent = substr(indent, 3)
}
function indent_print(line)
{
    print indent line
}
function set_last(inst,arg)
{
    last_inst = inst
    last_arg = arg
}
function reset_last()
{
    last_inst = ""
    last_arg = 0
}
function out_last(  diff)
{
    if (last_inst ~ /ix/) {
        if (last_arg > 1)
            diff = " += " last_arg
        else if (last_arg == 1)
            diff = "++"
        else if (last_arg == 0)
            diff = ""
        else if (last_arg == -1)
            diff = "--"
        else if (last_arg < -1)
            diff = " -= " (0 - last_arg)

        if (diff) indent_print(last_inst diff)
    }
    last_inst = ""
}
function set_incr_decr(s)
{
    if (last_inst == "ix") {
        if (last_arg == 1) { gsub(/ix/, "++ix", s); last_inst = "" }
        else if (last_arg == -1) { gsub(/ix/, "--ix", s); last_inst = "" }
    }
    return s
}

処理系間の差を吸収するため,入出力用の関数を受け取る関数を生成。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
function BF(code){
  var s = '', o = {
    '>': 'm[p+=?]|=0;',
    '<': 'm[p-=?]|=0;',
    '+': 'm[p]+=?;',
    '-': 'm[p]-=?;',
    ',': 'm[p]=get().charCodeAt(0);',
    '.': 'put(c(m[p]));',
    '[': 'while(m[p]){',
    ']': '}' };
  code.replace(/>+|<+|\++|-+|([,.[\]])/g, function($, _){
    s += _ ? o[$] : o[$.charAt(0)].replace('?', $.length) });
  return eval('0,function(get,put){var m=[0],p=0,c=String.fromCharCode;'+ s +'}');
}
/// Rhinoで「hello world」のテスト ///
BF('++++++++++[>+++++++>++++++++++>+++>+<<<<-]\
    >++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.'
)(function(){ return '?' }, function(c){ java.lang.System.out.print(c) });

/// WSHで「echo」のテスト ///
//BF(',+[-.,+]')(function(){ return WSH.stdIn.read(1) }, function(c){ WSH.stdOut.write(c) });

あまり綺麗なコードではないですが...。
メモリは左右に伸びる無限リストで表現しました。

>=>演算子 (Kleisli composition)は非常に便利です。
GHC6.8で新しく追加されましたが,一応定義を書きました。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import Data.List
import System.Environment

putCode '>' = putStr "incP >=> "
putCode '<' = putStr "decP >=> "
putCode '+' = putStr "inc >=> " 
putCode '-' = putStr "dec >=> " 
putCode '.' = putStr "put >=> " 
putCode ',' = putStr "get >=> " 
putCode '[' = putStr "loop("
putCode ']' = putStr "return) >=> "

genCode source 
    = do putStr "runBF = "
         mapM_ putCode (source `intersect` "><+-.,[]")
         putStrLn "return"

main = do
    source <- readFile . head =<< getArgs

    putStrLn "import Data.Char"
    putStrLn "(>=>) m1 m2 = \\s -> m1 s >>= m2"
    putStrLn "incP (ps, n:ns)    = return (n:ps, ns)"
    putStrLn "decP (p:ps, ns)    = return (ps, p:ns)"
    putStrLn "inc (ps, n:ns)     = return (ps, n+1:ns)"
    putStrLn "dec (ps, n:ns)     = return (ps, n-1:ns)"
    putStrLn "put mem@(_, n:_)   = putChar (chr n) >> return mem"
    putStrLn "get mem@(ps, _:ns) = getChar >> (\\c -> return (ps, ord c:ns))"
    putStrLn "loop code mem@(_, 0:_) = return mem"
    putStrLn "loop code mem = code mem >>= loop code"
    genCode source
    putStrLn "main = runBF ([0,0..], [0,0..])"

C++→Cの変化球. Xbyakを使ってオブジェクトコードを出力. ./bf hello.bf なら即実行 ./bf hello.bf 1 > a.c でx86 32bit用Cコードを生成します.これでも32bit x86ならMac/Win/Linuxで動作します. gccの場合は-fno-operator-namesをつけてコンパイルしてください.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
/*
    出力例
#include <stdio.h>
static int stack[32768];
static const unsigned char code[] = {
0x55,0x56,0x57,0x8b,0x74,0x24,0x10,0x8b,0x7c,0x24,0x14,0x8b,0x6c,0x24,0x18,0x83,
...
};
main()
{
    ((void (*)(void*, void*, int *))code)((void*)putchar, (void*)getchar, stack);
}

*/
#include "xbyak/xbyak.h"
#include <stdio.h>
#include <stack>
#include <fstream>

struct Brainfuck : public Xbyak::CodeGenerator {
    Brainfuck(std::istream& is) : CodeGenerator(10000)
    {
        push(ebp); // stack
        push(esi);
        push(edi);
        const int _P = 4 * 3;
        mov(esi, ptr[esp + _P + 4]);
        mov(edi, ptr[esp + _P + 8]);
        mov(ebp, ptr[esp + _P + 12]);
        int labelNo = 0;
        std::stack<int> keepLabelNo;
        char label[32];
        char c;
        while (is >> c) {
            switch (c) {
            case '+': inc(dword [ebp]); break;
            case '-': dec(dword [ebp]); break;
            case '.': push(dword [ebp]); call(esi); pop(eax); break;
            case ',': call(edi); mov(dword [ebp], eax); break;
            case '>': add(ebp, 4); break;
            case '<': sub(ebp, 4); break;
            case '[':
                sprintf(label, "B%d", labelNo); L(label);
                mov(eax, dword [ebp]);
                test(eax, eax);
                sprintf(label, "F%d", labelNo); jz(label, T_NEAR);
                keepLabelNo.push(labelNo++);
                break;
            case ']':
                {
                    int no = keepLabelNo.top(); keepLabelNo.pop();
                    sprintf(label, "B%d", no); jmp(label);
                    sprintf(label, "F%d", no); L(label);
                }
                break;
            default:
                break;
            }
        }
        pop(edi);
        pop(esi);
        pop(ebp);
        ret();
    }
};

void dump(const Xbyak::uint8 *code, size_t size)
{
    puts("#include <stdio.h>\nstatic int stack[32768];\nstatic const unsigned char code[] = {");
    for (size_t i = 0; i < size; i++) {
        printf("0x%02x,", code[i]); if ((i % 16) == 15) putchar('\n');
    }
    puts("\n};");
#ifdef __linux__
    puts("#include <unistd.h>");
    puts("#include <sys/mman.h>");
#endif
    puts("main()\n{");
#ifdef __linux__
    puts("\tlong pageSize = sysconf(_SC_PAGESIZE) - 1;");
    puts("\tmprotect((void*)code, (sizeof(code) + pageSize) & ~pageSize, PROT_READ | PROT_EXEC);");
#endif
    puts(
        "\t((void (*)(void*, void*, int *))code)((void*)putchar, (void*)getchar, stack);\n"
        "}"
    );
}

int main(int argc, char *argv[])
{
    if (argc == 1) {
        fprintf(stderr, "bf filename.bf [0|1]\n");
        return 1;
    }
    std::ifstream ifs(argv[1]);
    int mode = argc == 3 ? atoi(argv[2]) : 0; 
    Brainfuck bf(ifs);
    if (mode == 0) {
        static int stack[32768];
        ((void (*)(void*, void*, int *))bf.getCode())((void*)putchar, (void*)getchar, stack);
    } else {
        dump(bf.getCode(), bf.getSize());
    }
    return 0;
}

仮想機械(機械状態とインストラクションセット)を明示的に定義した.

機械状態は,プログラムカウンタのスタック,プログラムカウンタ,ヒープ,ヒープポインタの4つ組み.インストラクションはBFのインストラクションに対応する.

  • '>' → incp
  • '<' → decp
  • '+' → incp
  • '-' → decc
  • '.' → putc
  • ',' → getc
  • ']' → jmpb
  • '[' → jmpf <PC>

jmpf 以外はオペランドのないインストラクション.jmpfは飛び先をオペランドとする.

% runghc bfc.hs hello_world

とやるとhello_world.bというBFコードファイルを読み,インストラクション列をbfrts.templというランタイムテンプレートの最後に追加して,hello_world.hsというHaskellのファイルを作成する.上のhello_world.bをコンパイルしてできたインストラクション列は,

codeL :: [Instruction]

codeL = [incc,incc,incc,incc,incc,incc,incc,incc,jmpf 22,incp,incc,incc,incc,incc,incc,incc,incc,incc,incc,decp,decc,jmpb,incp,putc,decp,incc,incc,incc,incc,incc,jmpf 41,incp,incc,incc,incc,incc,incc,incc,decp,decc,jmpb,incp,decc,putc,incc,incc,incc,incc,incc,incc,incc,putc,putc,incc,incc,incc,putc,decp,incc,incc,incc,incc,incc,incc,incc,incc,jmpf 77,incp,incp,incc,incc,incc,incc,decp,decp,decc,jmpb,incp,incp,putc,decp,decp,incc,incc,incc,incc,jmpf 97,incp,decc,decc,decc,decc,decc,decc,decp,decc,jmpb,incp,putc,decp,incc,incc,incc,incc,jmpf 115,incp,incc,incc,incc,incc,incc,incc,decp,decc,jmpb,incp,putc,incc,incc,incc,putc,decc,decc,decc,decc,decc,decc,putc,decc,decc,decc,decc,decc,decc,decc,decc,putc,incp,incc,putc]

で,これがbfrts.templの最後の追加されて hello_world.hs が出きる.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
-- bfc.hs : BF コンパイラ ---------------------------------------------------
module Main where

import Data.Char
import Data.List
import System.IO
import System.Environment

main = do { args <- getArgs
          ; case args of
              []  -> runcompiler stdin stdout
              s:_ -> do { ih <- openFile (s++".b") ReadMode
                        ; oh <- openFile (s++".hs") WriteMode
                        ; runcompiler ih oh
                        }
          }

runcompiler ih oh =
 do { cs <- hGetContents ih
    ; let cs' = zip [0..] $ filter (not . isSpace) cs
    ; outputCode oh $ compile cs'
    }

compile [] = []
compile (c:cs) = case c of
  (_,'>') -> "incp" : compile cs
  (_,'<') -> "decp" : compile cs
  (_,'+') -> "incc" : compile cs
  (_,'-') -> "decc" : compile cs
  (_,'.') -> "putc" : compile cs
  (_,',') -> "getc" : compile cs
  (_,']') -> "jmpb" : compile cs
  (_,'[') -> ("jmpf "++show pc) : compile cs
               where pc = findpc [] cs
  _       -> error "invalid input"

findpc s []     = error "syntax error"
findpc s (c:cs) = case c of
  (_,'[') -> findpc ('[':s) cs
  (p,']') -> case s of
               [] -> p+1
               _  -> findpc (tail s) cs
  _       -> findpc s cs

outputCode h s 
 = do { hd <- readFile "bfrts.tmpl"
      ; hPutStrLn h hd
      ; hPutStrLn h "codeL :: [Instruction]"
      ; hPutStr h "codeL = ["
      ; hPutStr h (concat (intersperse "," s))
      ; hPutStr h "]\n"
      }

-- bfrts.templ : ランタイムテンプレート -------------------------------------

module Main where

import Control.Monad.State
import Data.Array as A
import Data.Char
import Data.IntMap as M
import Debug.Trace

type PC      = Int
type Code    = Array PC Instruction
type Heap    = M.IntMap Char
type Stack   = [PC]
type Pointer = Int
type VM   = (Stack,PC,Heap,Pointer)

type Instruction = VM -> StateT VM IO ()

top = head
pop = tail
push = (:)

-- initial machine state

iStat = ([],0,M.fromList $ zip [0..29999] (repeat $ chr 0),0)

-- instruction set

incp,decp,incc,decc,putc,getc,jmpb :: Instruction
jmpf :: PC -> Instruction
incp (s,c,h,p) = put (s,succ c,h,succ p)
decp (s,c,h,p) = put (s,succ c,h,pred p)
incc (s,c,h,p) = put (s,succ c,M.update (Just . succ) p h,p)
decc (s,c,h,p) = put (s,succ c,M.update (Just . pred) p h,p)
putc (s,c,h,p) = liftIO (putChar (h M.! p)) >> put (s,succ c,h,p)
getc (s,c,h,p) = liftIO (getChar >>= \ ch -> return (s,succ c,M.update (const (Just ch)) p h,p)) >>= put
jmpb (s,c,h,p) = if h M.! p == chr 0 then put (pop s,succ c,h,p) else put (s,top s,h,p)
jmpf pc (s,c,h,p) = if h M.! p == chr 0 then put (s,pc,h,p) else put (push (succ c) s,succ c,h,p)

-- run compiled bf code
             
main :: IO ()
main = evalStateT execute iStat >> putStrLn ""

run = do { stat@(s,c,h,p) <- get
         ; if inRange (bounds code) c then (code A.! c) stat >> run
           else return () 
         }

code :: Code 
code = listArray (0,length codeL - 1) codeL

-- この下にコンパイル済みの codeL :: [Instruction] が追加される

せっかくなので++++がbf.inc(4)に置き換わるような設計にしてみました。

この程度の規模なら正規表現でも十分いけるんじゃないかと思いつつ勉強がてらにlexとyaccを使いました。

PLY (Python Lex-Yacc)

あとインデントうんぬんを考慮しないといけないのはそもそも直接while文を使うからなので式で表現しました。

,+[-.,+]を入力するとbf.get()or bf.inc(1)or bf.loop(lambda: bf.inc(-1)or bf.put()or bf.get()or bf.inc (1))と出力されます。

下のFizzBuzzコードを食わせると2638バイトの出力で、処理時間はあっという間でした。 http://d.hatena.ne.jp/n_shuyo/20070516/fizzbuzz

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
from ply import lex, yacc
import sys

# lex
tokens = "PLUS MINUS LEFT RIGHT WHILE WEND PUT GET COMMENT".split()

t_WHILE = "\["
t_WEND = "]"
t_PUT = "\."
t_GET = ","

def t_PLUS(t):
    "\++"
    t.value = len(t.value)
    return t

def t_MINUS(t):
    "-+"
    t.value = len(t.value)
    return t

def t_LEFT(t):
    "<+"
    t.value = len(t.value)
    return t

def t_RIGHT(t):
    ">+"
    t.value = len(t.value)
    return t

def t_COMMENT(t):
    '[^[\]><+-,.]' # return nothing

def t_error(t): pass
    
# yacc
"""*** grammar definition
sequence : sequence command
         | command

command : PLUS
        | MINUS
        | LEFT
        | RIGHT
        | PUT
        | GET
        | loop

loop : WHILE sequence WEND
"""

def p_sequence(p):
    "sequence : sequence command"
    p[0] = p[1] + "or " + p[2]

def p_sequence_command(p):
    "sequence : command"
    p[0] = p[1]

def p_command_PLUS(p):
    "command : PLUS"
    p[0] = "bf.inc(%s)" % p[1]

def p_command_MINUS(p):
    "command : MINUS"
    p[0] = "bf.inc(%s)" % -p[1]

def p_command_LEFT(p):
    "command : LEFT"
    p[0] = "bf.mov(%s)" % -p[1]

def p_command_RIGHT(p):
    "command : RIGHT"
    p[0] = "bf.mov(%s)" % p[1]

def p_command_PUT(p):
    "command : PUT"
    p[0] = "bf.put()"

def p_command_GET(p):
    "command : GET"
    p[0] = "bf.get()"

def p_command_loop(p):
    "command : loop"
    p[0] = p[1]
    
def p_loop(p):
    "loop : WHILE sequence WEND"
    p[0] = "bf.loop(lambda: %s)" % p[2]

def p_error(p): pass

# input and parse
data = sys.stdin.read()
lex.lex()
yacc.yacc()
result = yacc.parse(data)

# output
print """
from collections import defaultdict
import sys
class BF(object):
    mem = defaultdict(int)
    cur = 0
    def inc(self, n):
        self.mem[self.cur] += n
        self.mem[self.cur] %= 256
    def mov(self, n):
        self.cur += n
    def put(self):
        c = chr(self.mem[self.cur])
        sys.stdout.write(c)
    def get(self):
        c = sys.stdin.read(1)
        self.mem[self.cur] = ord(c)
    def loop(self, seq):
        while self.mem[self.cur]:
            seq()

bf = BF()
"""

print result

素数探索のコード:

http://labs.cybozu.co.jp/blog/kazuho/archives/2006/06/bf_prime.php

さすがに結構時間がかかるなー。Core2Duo @2.4GHzで1分くらい。

real    1m2.317s
user    0m0.015s
sys     0m0.031s

8行修正して[-]を単体で「リセット命令」にしたらreal 0m54.380sになった。

うーん、これ以上高速化するとなるとコピーの処理を置き換えるとかになりそうだけど、それは正規表現では無理な気がするなぁ。パースしながら出力文字列を作るのをやめて、sequenceを文字列に変換する際にmovとincだけで構成されているかどうかをチェックして…となるのかな。面倒だな。


#3964-#3978を、optimize=2 を指定すると身も蓋もなく最適化を行うように改良(?)したもの。

- http://namazu.org/~takesako/ppencode/bpencode.html
- http://pc11.2ch.net/test/read.cgi/tech/1177988460/135
あたりのソースを通してみると面白いです。

% cat helloworld.bf
++++++++++[>+++++++>++++++++++>+++>+<<<<-]
>++.>+.+++++++..+++.>++.<<+++++++++++++++.
>.+++.------.--------.>+.>.

% awk -v optimize=1 -f bf2awk.awk < helloworld.bf
BEGIN {
  ix = 0
  st[ix] += 10
  while (st[ix]) {
    st[++ix] += 7
    st[++ix] += 10
    st[++ix] += 3
    st[++ix]++
    ix -= 4
    st[ix]--
  }
  st[++ix] += 2
  printf("%c", st[ix])
  printf("%c", ++st[++ix])
  st[ix] += 7
  printf("%c", st[ix])
  printf("%c", st[ix])
  st[ix] += 3
  printf("%c", st[ix])
  st[++ix] += 2
  printf("%c", st[ix])
  ix -= 2
  st[ix] += 15
  printf("%c", st[ix])
  printf("%c", st[++ix])
  st[ix] += 3
  printf("%c", st[ix])
  st[ix] -= 6
  printf("%c", st[ix])
  st[ix] -= 8
  printf("%c", st[ix])
  printf("%c", ++st[++ix])
  printf("%c", st[++ix])
}

% awk -v optimize=2 -f bf2awk.awk < helloworld.bf
BEGIN {
  print "Hello World!"
}
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
#
# Brainf*ck to AWK compiler, written in AWK by naoya_t/
#
BEGIN {
    inst_buf = ""
    ix = 0
}
{
    gsub(/#.*$/,""); # comment
    gsub(/[^\[\]<>+\-.,]/,""); if (/^$/) next
    inst_buf = inst_buf $0
}
END {
    if (inst_buf ~ /,/) getchar_used = 1

    # compile
    if (optimize >= 1) reset_last()
    output_buf = ""

    indent = ""
    print "BEGIN {"

    indent_incr()
    if (getchar_used) indent_print("read_buf = \"\"")
    if (getchar_used || optimize < 2) indent_print("ix = 0")

    diff_value[">"] = diff_value["+"] = 1
    diff_value["<"] = diff_value["-"] = -1
    incr_decr[">"] = incr_decr["+"] = "++"
    incr_decr["<"] = incr_decr["-"] = "--"

    N = split(inst_buf, ops, "")
    eval(ops,1,N)

    if (optimize >= 1) out_last()
    flush_buf(0)

    indent_decr()
    indent_print("}")

    if (getchar_used) output_getchar_funcs()
}

function eval(ops,begin,end,  i,j,op,s,till)
{
    for (i=begin; i<=end; i++) {
        op = ops[i]
        if (op ~ /[><]/) {
            ix += diff_value[op] # emulation
            if (optimize >= 1) {
                if (last_inst == "ix") {
                    last_arg += diff_value[op]
                } else {
                    out_last()
                    set_last("ix", diff_value[op])
                }
            } else {
                if (getchar_used || optimize < 2) indent_print("ix" incr_decr[op])
            }
        } else if (op ~ /[+-]/) {
            st[ix] += diff_value[op] # emulation
            if (optimize >= 1) {
                if (last_inst ~ /^st\[/) {
                    last_arg += diff_value[op]
                } else {
                    s = set_incr_decr("st[ix]")
                    if (last_inst) out_last()
                    set_last(s, diff_value[op])
                }
            } else {
                if (getchar_used || optimize < 2) indent_print("st[ix]" incr_decr[op])
            }
        } else if (op == ".") {
            s = "printf(\"%c\", st[ix])"
            if (optimize >= 1) {
                s = set_incr_decr(s)
                if (last_inst) out_last()
            }
            if (getchar_used || optimize < 2)
                indent_print(s)
            else
                putchar(st[ix])
        } else if (op == ",") {
            s = "st[ix] = getchar()"
            if (optimize >= 1) {
                s = set_incr_decr(s)
                if (last_inst) out_last()
            }
            indent_print(s)
        } else if (op == "[") {
            nest = 1
            for (j=i+1; j<=N; j++) {
                if (ops[j] == "[") nest++
                if (ops[j] == "]") nest--
                if (nest == 0) { till = j ; break }
            }
            while_loop(ops, i+1, till-1)
            i = till
        } else {
            ;
        }
    }
}
function while_loop(ops,begin,end,  i,j,putchar_used)
{
    putchar_used = 0
    for (j=begin; j<=end; j++) if (ops[j] == ".") { putchar_used = 1; break }

    if (getchar_used || optimize < 2) {
        out_last()
        indent_print("while (st[ix]) {")
        indent_incr()

        eval(ops,begin,end)

        if (optimize >= 1) out_last()
        indent_decr()
        indent_print("}")
    } else {
        while (st[ix]) eval(ops,begin,end)
    }
}

function putchar(ch)
{
    if (ch == 10 || ch == 13) {
        flush_buf(1)
    } else {
        output_buf = output_buf sprintf("%c", st[ix])
    }
}
function flush_buf(cr)
{
    if (output_buf) {
        gsub(/\\/, "\\\\", output_buf)
        if (cr)
            indent_print("print \"" output_buf "\"")
        else
            indent_print("printf(\"%s\", \"" output_buf "\")")
        output_buf = ""
    }
}

function indent_incr()
{
    indent = "  " indent
}
function indent_decr()
{
    indent = substr(indent, 3)
}
function indent_print(line)
{
    print indent line
}
function set_last(inst,arg)
{
    last_inst = inst
    last_arg = arg
}
function reset_last()
{
    last_inst = ""
    last_arg = 0
}
function out_last(  diff)
{
    if (last_inst ~ /ix/) {
        if (last_arg > 1)
            diff = " += " last_arg
        else if (last_arg == 1)
            diff = "++"
        else if (last_arg == 0)
            diff = ""
        else if (last_arg == -1)
            diff = "--"
        else if (last_arg < -1)
            diff = " -= " (0 - last_arg)

        if (diff && (getchar_used || optimize < 2)) indent_print(last_inst diff)
    }
    last_inst = ""
}
function set_incr_decr(s)
{
    if (last_inst == "ix") {
        if (last_arg == 1) { gsub(/ix/, "++ix", s); last_inst = "" }
        else if (last_arg == -1) { gsub(/ix/, "--ix", s); last_inst = "" }
    } else if (last_inst ~ /^st\[/ && s ~ /printf/) {
        if (last_arg == 1) { gsub(/st\[ix\]/, "++" last_inst, s); last_inst = "" }
        else if (last_arg == -1) { gsub(/st\[ix\]/, "--" last_inst, s); last_inst = "" }
    }

    return s
}

function output_getchar_funcs()
{
    print "function getchar(  ch)"
    print "{"
    print "  if (read_buf ~ /^$/) {"
    print "    if (eof) return -1"
#    print "    if ((getline read_buf) < 1) { eof = 1; return -1 }"
    print "    if (getline != 1) { eof = 1; return -1 } ; read_buf = $0" # a2p
    print "  }"
    print "  ch = ord(substr(read_buf,1,1))"
    print "  read_buf = substr(read_buf,2)"
    print "  return ch"
    print "}"
    
    print "function ord(ch, ofs)"
    print "{"
    print "  if (ch == \"\\t\") return 9"
    print "  if (ch == \"\\n\") return 13"
#    print "  ofs = index(\" !\\\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\", ch)"
    print "  if (ch == \"@\") return 64" # a2p
    print "  ofs = index(\" !\\\"#$%&'()*+,-./0123456789:;<=>?.ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\", ch)" # a2p
    print "  return ofs ? 31 + ofs : 0"
    print "}"
}

にしおさんに倣って,若干最適化コードを生成するようにしました.素数計算のだと3割ぐらい出力コード長が短くなるようです.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
+       int getContinuousChar(std::istream& is, char c)
+       {
+               int count = 1;
+               char p;
+               while (is >> p) {
+                       if (p != c) break;
+                       count++;
+               }
+               is.unget();
+               return count;
+       }

+#if 0
        case '+': inc(dword [ebp]); break;
        case '-': dec(dword [ebp]); break;
        case '>': add(ebp, 4); break;
        case '<': sub(ebp, 4); break;
#else
       case '+':
       case '-':
               {
                       int count = getContinuousChar(is, c);
                       if (count == 1) {
                               c == '+' ? inc(dword [ebp]) : dec(dword [ebp]);
                       } else {
                               add(dword [ebp], (c == '+' ? count : -count));
                       }
               }
               break;
       case '>':
       case '<':
               {
                       int count = getContinuousChar(is, c);
                       add(ebp, 4 * (c == '>' ? count : -count));
               }
               break;
#endif

Pythonだとこれだけ時間がかかるんですね。
参考になります。

Scalaでもやってみたところ、#3960のコードで
出力したものを、コンパイルせずインタプリタで
実行しても1秒以内に結果が出ました。
Scalaは実はできる子です(笑

素直に変換。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import std.stdio;
import std.file;
import std.string;

void main(string[] args){
    string bfCode = cast(string)read(args[1]);
    string[] buf;
    uint ptrIndex;
    uint maxPtrIndex;
    uint indentLevel = 1;

    void addLine(string line){
        buf ~= repeat("\t", indentLevel) ~ line;
    }

    foreach(c; bfCode){
        switch(c){
            case '>':
                addLine("ptr++;");
                if(++ptrIndex > maxPtrIndex){
                    maxPtrIndex++;
                }
                break;
            case '<':
                addLine("ptr--;");
                ptrIndex--;
                break;
            case '+':
                addLine("mem[ptr]++;");
                break;
            case '-':
                addLine("mem[ptr]--;");
                break;
            case '.':
                addLine("putchar(cast(char)mem[ptr]);");
                break;
            case ',':
                addLine("mem[ptr] = cast(ubyte)getchar();");
                break;
            case '[':
                addLine("while(mem[ptr]){");
                indentLevel++;
                break;
            case ']':
                indentLevel--;
                addLine("}");
                break;
            default:
        }
    }
    writefln("import std.c.stdio;");
    writefln("void main(){");
    writefln("\tubyte mem[", maxPtrIndex + 1, "];");
    writefln("\tuint ptr;");
    writefln(buf.join("\n"));
    writefln("}");
}

こんなもの dict にするのがいいに決まっている、と思ったら Dan さんに先こされているし。
仕方がないので、インデント情報も dict に突っ込んだやつを。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
from __future__ import with_statement
import sys

def brainfuck_compile(source):
    try:
        from cStringIO import StringIO
    except:
        from StringIO import StringIO

    actions = {
        '>': ('pointer += 1', 0),
        '<': ('pointer -= 1', 0),
        '+': ('tape[pointer] = tape.get(pointer, 0) + 1', 0),
        '-': ('tape[pointer] = tape.get(pointer, 0) - 1', 0),
        '.': ('sys.stdout.write(chr(tape.get(pointer, 0)))', 0),
        ',': ('tape[pointer] = sys.stdin.read(1)', 0),
        '[': ('while tape.get(pointer, 0):', 1),
        ']': ('', -1),
    }

    generated = StringIO()
    print >>generated, 'import sys'
    print >>generated, 'tape, pointer = dict(), 0'
    indent = 0
    for c in source:
        if c.isspace(): continue
        stmt, indent_diff = actions[c]
        print >>generated, '%*s%s' % (indent, '', stmt)
        indent += indent_diff

    return generated.getvalue()

def iterchar(fp):
    for line in fp:
        for c in line: yield c

def main(args):
    if args:
        for arg in args:
            with file(arg) as fp:
                code = brainfuck_compile(iterchar(fp))
                print code
    else:
        code = brainfuck_compile(iterchar(sys.stdin))
        print code

if __name__ == '__main__':
    main(sys.argv[1:])

うーん、それはちょっと違いますね。 #3960のコードはコードを一つの関数の中にローカルに展開しているのに対して、上のコードは一つ一つの命令がメソッド呼び出しですから。PythonとScalaの性能の違いと言うより、生成されたコードの質の違い思います。

# 要するに僕のコードが生成したのは質が悪いと!orz


毎回bf.fooとメソッド名の解決をしているのをやめるとreal:0m32.481sになり、きちんとインデントするようにしたら(毎回関数呼び出しをするのをやめたら)real:0m15.548sになりました。

それでもScalaには全然追いつかないのか…orz


これ

tape[ptr] = sys.stdin.read(1)

の部分でordしていないのでテープに文字列が書き込まる気が。


コードを張り忘れたので。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
def p_sequence(p):
    "sequence : sequence command"
    p[0] = p[1] + p[2]

def p_sequence_command(p):
    "sequence : command"
    p[0] = p[1]

def p_command_PLUS(p):
    "command : PLUS"
    p[0] = "\nmem[cur] = (mem[cur] + %s) %% 256" % p[1]

def p_command_MINUS(p):
    "command : MINUS"
    p[0] = "\nmem[cur] = (mem[cur] - %s) %% 256" % p[1]

def p_command_LEFT(p):
    "command : LEFT"
    p[0] = "\ncur -= %s" % p[1]

def p_command_RIGHT(p):
    "command : RIGHT"
    p[0] = "\ncur += %s" % p[1]

def p_command_PUT(p):
    "command : PUT"
    p[0] = "\nsys.stdout.write(chr(mem[cur]))"

def p_command_GET(p):
    "command : GET"
    p[0] = "\nmem[cur] = ord(sys.stdin.read(1))"

def p_command_RESET(p):
    "command : RESET"
    p[0] = "\nmem[cur] = 0"

def p_command_loop(p):
    "command : loop"
    p[0] = p[1]
    
def p_loop(p):
    "loop : WHILE sequence WEND"
    p[0] = "\nwhile mem[cur]:%s" % p[2].replace("\n", "\n    ")

def p_error(p): pass

# input and parse
data = sys.stdin.read()
lex.lex()
yacc.yacc()
result = yacc.parse(data)

# output
print """
from collections import defaultdict
import sys
mem = defaultdict(int)
cur = 0
"""

print result

あ、確かにそうですね。見落としてました。

せっかくなので#3952の素朴なタイプでも手元で時間とって見ましたが、

dppさんの#3952:55秒程度
にしおさんの#3988:2分10秒程度

でした。

あと、メモリとしてdictを使っているけど、
[0]*0xffffみたいにリストで長さ決めうちにすると
だいぶマシですね。55 -> 31秒程度まで短縮できました。

正規表現とハッシュの合わせ技で
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
use strict;
use warnings;

sub compile {
    my $source = shift;
    my $out = '';

    my $code = 'sub{no warnings;my(@t, $p, @o);';

    my %patterns = (
        '>' => sub { sprintf '$p+=%d;', length $_[0] },
        '<' => sub { sprintf '$p-=%d;', length $_[0] },
        '+' => sub { sprintf '$t[$p]+=%s;', length $_[0] },
        '-' => sub { sprintf '$t[$p]-=%d;', length $_[0] },
        '.' => sub { sprintf 'push @o,$t[$p];' },
        ',' => sub { sprintf '$t[$p] = shift;' },
        '[' => sub { 'while($t[$p]){' },
        ']' => sub { '}' },
    );
    my $re = qr/>+|<+|\++|-+|\.|,|\[|\]/;

    my $tmp = $source;
    $tmp =~ tr/<>+\-,.[]//cd;
    $tmp =~ s/$re/$patterns{substr($&, 0, 1)}->($&)/ge;

    $code .= $tmp;
    $code .= '@o;}';
}

sub main {
    my $source = do { local $/; <> };
    my $code = compile($source);

    my @out = do { eval $code }->();
    print pack('c*', @out);
}

main;

引数にBFのプログラムを与えて実行します。

java Sample '>,----------[>,----------]<[++++++++++.<]'

BF.javaという名前のファイルが作られるので、さらにjavacでコンパイルします。
上記の例は入力を逆順に出力するプログラム(入力の終了は改行(\n))です。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.io.*;
import java.util.*;

public class Sample {
    static final String NL = System.getProperty("line.separator");
    static final String HEADER = "import java.io.*;"+NL+NL+"public class BF {"+NL+
        "    static final int SIZE = 32768;"+NL+"    static byte[] a = new byte[SIZE];"+NL+
        "    public static void main(String[] args) throws IOException {"+NL+
        "\tBufferedReader r = new BufferedReader(new InputStreamReader(System.in));"+NL+
        "\tint ptr = 0;";
    static final String FOOTER = "\tSystem.out.println();"+NL+"    }"+NL+"}";
    static HashMap<Character, String> bfCode = new HashMap<Character, String>();
    static {
        bfCode.put('>', "\tptr++;");
        bfCode.put('<', "\tptr--;");
        bfCode.put('+', "\ta[ptr]++;");
        bfCode.put('-', "\ta[ptr]--;");
        bfCode.put('.', "\tSystem.out.write(a[ptr]);");
        bfCode.put(',', "\ta[ptr] = (byte)r.read();");
        bfCode.put('[', "\twhile (a[ptr] != 0) {");
        bfCode.put(']', "\t}");
    }
    public static void main(String[] args) throws IOException {
        PrintWriter w = new PrintWriter("BF.java");
        w.println(HEADER);
        for (char c : args[0].toCharArray()) {
            String code = bfCode.get(c);
            if (code != null)
                w.println(code);
        }
        w.println(FOOTER);
        w.close();
    }
}


	
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include <iostream>
#include <fstream>
#include <cctype>
using namespace std;

#define REP(n) for (size_t i = 0; i < n; ++i)

void compile(istream& in, ostream& out)
{
    size_t indent = 0;

#define OUTPUT(s) REP(indent) out << "\t"; out << s << endl;

    OUTPUT("#include <stdio.h>");
    OUTPUT("#include <mem.h>");
    OUTPUT("");
    OUTPUT("int main()");
    OUTPUT("{");
    ++indent;
    OUTPUT("unsigned char buf[33000], *p = buf;");
    OUTPUT("memset(buf, 0, sizeof(buf));");
    OUTPUT("");

    char c;

    while (in.get(c))
    {
        if (isspace(c))
        {
            // pass
        }
        else if (c == '<')
        {
            OUTPUT("--p;");
        }
        else if (c == '>')
        {
            OUTPUT("++p;");
        }
        else if (c == '+')
        {
            OUTPUT("++*p;");
        }
        else if (c == '-')
        {
            OUTPUT("--*p;");
        }
        else if (c == '.')
        {
            OUTPUT("putchar(*p);");
        }
        else if (c == ',')
        {
            OUTPUT("*p = getchar();");
        }
        else if (c == '[')
        {
            OUTPUT("while (*p)");
            OUTPUT("{");
            ++indent;
        }
        else if (c == ']')
        {
            --indent;
            OUTPUT("}");
        }
    }

    --indent;
    OUTPUT("}");

#undef OUTPUT
}

int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        cerr << "usage: name" << endl;

        return -1;
    }

    compile(ifstream(argv[1]), cout);
}

スタックオーバーフローすると反対側に出ます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
let parse = function
  | '>' -> "stackincr p;"
  | '<' -> "stackdecr p;"
  | '+' -> "incr code.(!p);"
  | '-' -> "decr code.(!p);"
  | '.' -> "print_char (char_of_int !(code.(!p)));"
  | ',' -> "code.(!p) := read_char ();"
  | '[' -> "while 0 <> !(code.(!p)) do"
  | ']' -> "done;;"
  |  _  -> ""  ;;

let main ?(stacksize=256) filename =
  let file = open_in filename in
  Printf.printf "\
    let stackincr x = if %d < (incr x; !x) then x:=0;;\n\
    let stackdecr x = if 0 > (decr x; !x) then x:=%d;;\n\
    let read_char () = int_of_char (read_line () ).[0];;\n\
    let code = Array.init %d (fun i -> ref 0);;\n\
    let p = ref 0;;\n\n"
    (stacksize-1) (stacksize-1) stacksize;
      
  let rec read_loop () =
    try
      let next = input_line file in
      String.iter (fun x -> print_endline (parse x)) next;
      read_loop ();
    with
      End_of_file -> close_in file   
  in read_loop ();;
  

if !(Sys.interactive)=false then
  match (Array.length Sys.argv) with
  | n when n=2 -> main Sys.argv.(1)
  | n when 2<n -> main ~stacksize:(int_of_string Sys.argv.(2)) Sys.argv.(1)
  | n          -> print_endline "usage: ocaml bf2ml.ml [stacksize] filename"
;;

ああ、スタックじゃなくてー

リングバッファ?


',' の処理が変じゃありません?
echo を試してみましたが、各行頭の一文字しか出力されませんでした。

書き換えてみました。echo はそれらしい動きをしています。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@@ -4,7 +4,7 @@
   | '+' -> "incr code.(!p);"
   | '-' -> "decr code.(!p);"
   | '.' -> "print_char (char_of_int !(code.(!p)));"
-  | ',' -> "code.(!p) := read_char ();"
+  | ',' -> "code.(!p) := input_byte stdin;"
   | '[' -> "while 0 <> !(code.(!p)) do"
   | ']' -> "done;;"
   |  _  -> ""  ;;
@@ -14,7 +14,6 @@
   Printf.printf "\
     let stackincr x = if %d < (incr x; !x) then x:=0;;\n\
     let stackdecr x = if 0 > (decr x; !x) then x:=%d;;\n\
-    let read_char () = int_of_char (read_line () ).[0];;\n\
     let code = Array.init %d (fun i -> ref 0);;\n\
     let p = ref 0;;\n\n"
     (stacksize-1) (stacksize-1) stacksize;

Wikipediaの説明をそのままコードにしただけですが・・・
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
bf <- function(str){
    cat("ptr <- 1", "\n")
    cat("buf <- numeric(1)", "\n")
    for(c in unlist(strsplit(str, ''))){
        switch(c,
            '>' = cat("ptr <- ptr+1; if(is.na(buf[ptr])) buf[ptr] <- 0", "\n"),
            '<' = cat("ptr <- ptr-1; if(is.na(buf[ptr])) buf[ptr] <- 0", "\n"),
            '+' = cat("buf[ptr] <- buf[ptr]+1", "\n"),
            '-' = cat("buf[ptr] <- buf[ptr]-1", "\n"),
            '.' = cat("cat(rawToChar(as.raw(buf[ptr])))", "\n"),
            ',' = cat("buf[ptr] <- as.integer(charToRaw(readLines(n=1)))", "\n"),
            '[' = cat("while(buf[ptr]){", "\n"),
            ']' = cat("}", "\n"))
    }
}

トランスレーターといえばcamlp4ということで、camlp4の3.10版で書いてみました。

LexerはBF用に書いていますが、TokenはOCaml用のものを流用しています。

Parserのコンパイル:
 ocamlc -c -I +camlp4 -pp camlp4orf dk80.ml

Parserを利用したBFファイルのトランスレート結果の確認:
 camlp4orf dk80.cmo -impl helloworld.bf 

Parserを利用したBFファイルのコンパイル:
 ocamlc -o dk80 -pp "camlp4 -printer Camlp4OCamlPrinter dk80.cmo -impl" -impl helloworld.bf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
open Camlp4.PreCast

module MyLexer = struct
  module Loc = Loc
  module Token = Token
  module Error = Token.Error

  let mk () loc sc =
    let rec next n = match Stream.peek sc with
      | None   -> Some(EOI, loc)
      | Some x -> let _ = Stream.junk sc in (match x with
        |'>'|'<'|'+'|'-'|'.'|','|'['|']'-> Some((SYMBOL (Printf.sprintf "%c" x)), loc)
        | _                             -> next n)
    in Stream.from next
end

module BF = MakeGram MyLexer

let expr = BF.Entry.mk "expr"
let repr = BF.Entry.mk "repr"
let term = BF.Entry.mk "term"

EXTEND BF
  expr: [[
      t = repr; `EOI     -> <:str_item<
        let p = ref 0 in
        let a = Array.create 1024 0 in
        let inc a p = a.(!p) := a.(!p) + 1 in
        let dec a p = a.(!p) := a.(!p) - 1 in
        let prt a p = print_char (char_of_int a.(!p)) in
        ( $t$ )
      >>
  ]];

  repr: [[
      t = term           -> t
    | t = term; u = repr -> <:expr< $t$; $u$ >>
  ]];

  term: [[
      ">"                -> <:expr< incr  p >>
    | "<"                -> <:expr< decr  p >>
    | "+"                -> <:expr< inc a p >>
    | "-"                -> <:expr< dec a p >>
    | "."                -> <:expr< prt a p >>
    | ","                -> <:expr< a.(!p) := input_byte stdin >>
    | "["; t = repr; "]" -> <:expr< while a.(!p) <> 0 do ( $t$ ) done >>
  ]];
END

let myparse  ?(directive_handler = fun _ -> None) _loc cs = BF.parse expr _loc cs
let myparse2 ?(directive_handler = fun _ -> None) _loc _  = <:sig_item< >>

let _ = Camlp4.Register.register_parser myparse myparse2;

Wikipedia と dankogai さんの実装を見つつ、C# で書いてみました。誰か添削してくださいw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
using System;
using System.Collections.Generic;

namespace Kinoko
{
    /*
        BrainF*ck
    */
    class Program
    {
        public static void Main(string[] args)
        {
            LinkedList<int> Tape = new LinkedList<int>();
            Tape.AddLast(0);
            LinkedListNode<int> Head = Tape.First;

            for(int i=0; i < args[0].Length; i++)
            {
                String op = args[0].Substring(i,1);
                switch(op)
                {
                    case "+":
                    {
                        Head.Value++;
                        break;
                    }
                    case "-":
                    {
                        Head.Value--;
                        break;
                    }
                    case "<":
                    {
                        if(Head == Tape.First)
                        {
                            Tape.AddFirst(0);
                        }
                        Head=Head.Previous;
                        break;
                    }
                    case ">":
                    {
                        if(Head == Tape.Last)
                        {
                            Tape.AddLast(0);
                        }
                        Head=Head.Next;
                        break;
                    }
                    case "[":
                    {
                        if(Head.Value == 0)
                        {
                            int nest = 1;
                            int step = 1;
                            while(nest > 0)
                            {
                                if(args[0].Substring(i+step, 1) == "[")
                                {
                                    nest++;
                                }
                                else if(args[0].Substring(i+step, 1) == "]")
                                {
                                    nest--;
                                }
                                step++;
                                if(i+step > args[0].Length)
                                {
                                    break;
                                }
                            }
                            i += step;
                        }
                        break;
                    }
                    case "]":
                    {
                        if(Head.Value != 0)
                        {
                            int nest = 1;
                            int step = 1;
                            while(nest > 0)
                            {
                                if(args[0].Substring(i-step, 1) == "]")
                                {
                                    nest++;
                                }
                                else if(args[0].Substring(i-step, 1) == "[")
                                {
                                    nest--;
                                }
                                step++;
                                if(i-step < 0)
                                {
                                    break;
                                }
                            }
                            i -= step;
                        }
                        break;
                    }
                    case ".":
                    {
                        Console.Write("{0}", ((Char)Head.Value));
                        break;
                    }
                    case ",":
                    {
                        int input = 0;
                        Console.Write("input:");
                        input = Console.Read();
                        Head.Value = input;
                        break;
                    }
                    default:
                    {
                        break;
                    }
                }
            }
        }
    }
}

unsafeを使って、 C#でポインタを使ってみました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
using System;
using System.IO;

namespace BrainFuck
{
    class Program
    {
        static void Main(string[] args)
        {
            StreamWriter sw = new StreamWriter("C:\\txt.txt");

            sw.WriteLine("using System;");
            sw.WriteLine("namespace BrainFuck{");
            sw.WriteLine("class Program{");
            sw.WriteLine("unsafe static void Main(string[] args){");
            sw.WriteLine("fixed (byte* b = new byte[1024]){");
            sw.WriteLine("byte* ptr = b;");

            foreach (char s in args[0])
            {
                switch (s)
                {
                    case '>':
                        sw.WriteLine("ptr++;");
                        break;
                    case '<':
                        sw.WriteLine("ptr--;");
                        break;
                    case '+':
                        sw.WriteLine("(*ptr)++;");
                        break;
                    case '-':
                        sw.WriteLine("(*ptr)--;");
                        break;
                    case '.':
                        sw.WriteLine("Console.Write((char)*ptr);");
                        break;
                    case ',':
                        sw.WriteLine("*ptr = Console.Read();");
                        break;
                    case '[':
                        sw.WriteLine("while(*ptr != 0){");
                        break;
                    case ']':
                        sw.WriteLine("}");
                        break;
                }
            }

            sw.WriteLine("}}}}");
            sw.Close();
        }
    }
}

strictもwarningsも通るけど一文。

Dan the Brainf.cker

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#!/usr/local/bin/perl
use strict;
use warnings;
s/(.)/do{no warnings 'syntax';my %c=qw!
< $p--;
> $p++;
+ $d[$p]++;
- $d[$p]--;
. print(chr$d[$p]);
, $d[$p]=getc;
[ while($d[$p]){
] }
!;\%c}->{$1}/egx and print for(<>);

ポインタとメモリ領域を,文字列stackの中に置き,regexpなどでstackを操作しつつ動作するようなコードを吐きます.
例)
% cat hello.bf
++++++++++[>+++++++>++++++++++>+++>+<<<<-]
>++.>+.+++++++..+++.>++.<<+++++++++++++++.
>.+++.------.--------.>+.>.

% make
ruby bfcompile.rb < hello.bf > hello.rb
ruby hello.rb
Hello World!

% cat hello.rb
class String
  def until_nz
    while self[self[0] + 1] != 0
      replace(yield(self))
    end
    self
  end

  def refer
    self.sub(/\A(.)(.*)\Z/m) do
      $1 + $2.sub(/^(.{#{$1[0]}})(.)/m) do
        $1 + yield($2)
      end
    end
  end
end

stack = "\000" + "\000" * 256
stack.refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
until_nz {|stack|
stack.
sub(/./m) {|p| (p[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
sub(/./m) {|p| (p[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
sub(/./m) {|p| (p[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
sub(/./m) {|p| (p[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
sub(/./m) {|p| (p[0] - 1).chr }.
sub(/./m) {|p| (p[0] - 1).chr }.
sub(/./m) {|p| (p[0] - 1).chr }.
sub(/./m) {|p| (p[0] - 1).chr }.
refer {|mem| (mem[0] - 1).chr }.
to_s
}.
sub(/./m) {|p| (p[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| putc(mem) }.
sub(/./m) {|p| (p[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| putc(mem) }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| putc(mem) }.
refer {|mem| putc(mem) }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| putc(mem) }.
sub(/./m) {|p| (p[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| putc(mem) }.
sub(/./m) {|p| (p[0] - 1).chr }.
sub(/./m) {|p| (p[0] - 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| putc(mem) }.
sub(/./m) {|p| (p[0] + 1).chr }.
refer {|mem| putc(mem) }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| putc(mem) }.
refer {|mem| (mem[0] - 1).chr }.
refer {|mem| (mem[0] - 1).chr }.
refer {|mem| (mem[0] - 1).chr }.
refer {|mem| (mem[0] - 1).chr }.
refer {|mem| (mem[0] - 1).chr }.
refer {|mem| (mem[0] - 1).chr }.
refer {|mem| putc(mem) }.
refer {|mem| (mem[0] - 1).chr }.
refer {|mem| (mem[0] - 1).chr }.
refer {|mem| (mem[0] - 1).chr }.
refer {|mem| (mem[0] - 1).chr }.
refer {|mem| (mem[0] - 1).chr }.
refer {|mem| (mem[0] - 1).chr }.
refer {|mem| (mem[0] - 1).chr }.
refer {|mem| (mem[0] - 1).chr }.
refer {|mem| putc(mem) }.
sub(/./m) {|p| (p[0] + 1).chr }.
refer {|mem| (mem[0] + 1).chr }.
refer {|mem| putc(mem) }.
sub(/./m) {|p| (p[0] + 1).chr }.
refer {|mem| putc(mem) }.
to_s
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
puts <<'EOS'
class String
  def until_nz
    while self[self[0] + 1] != 0
      replace(yield(self))
    end
    self
  end

  def refer
    self.sub(/\A(.)(.*)\Z/m) do
      $1 + $2.sub(/^(.{#{$1[0]}})(.)/m) do
        $1 + yield($2)
      end
    end
  end
end

stack = "\000" + "\000" * 256
EOS

print 'stack.'

code = {
  '>' => [ 'sub(/./m) {|p| (p[0] + 1).chr }.' ],
  '<' => [ 'sub(/./m) {|p| (p[0] - 1).chr }.' ],
  '+' => [ 'refer {|mem| (mem[0] + 1).chr }.' ],
  '-' => [ 'refer {|mem| (mem[0] - 1).chr }.' ],
  '.' => [ 'refer {|mem| putc(mem) }.' ],
  ',' => [ 'refer { STDIN.getc }.' ],
  '[' => [ 'until_nz {|stack|', 'stack.' ],
  ']' => [ 'to_s', '}.' ],
}
while ch = STDIN.getc
  next if code[ch.chr].nil?
  puts code[ch.chr]
end
puts 'to_s'

少しだけ最適化を行います。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <stdio.h>

void putnest(FILE* fout,int n){
    while(n--){
        fprintf(fout,"\t");
    };
}

int main(){
    char filename[]="test.bf";
    FILE *fin;
    FILE *fout;
    int cmd=' ',lastcmd;
    int count=0;
    int nest=1;
    fin =fopen(filename,"r");
    fout=fopen("bf.c","w");
    
    fprintf(fout,"#include<stdio.h>\n");
    fprintf(fout,"\n");
    fprintf(fout,"int main(){\n");
    fprintf(fout,"\tint  buf[256];\n");
    fprintf(fout,"\tint *ptr;\n");
    fprintf(fout,"\tint i;");
    fprintf(fout,"\n");
    fprintf(fout,"\tfor(i=0;i<256;i++) buf[i]=0;\n");
    fprintf(fout,"\tptr=buf;\n");
    fprintf(fout,"\n");
    
    do{
        lastcmd=cmd;
        cmd=fgetc(fin);
        if(lastcmd!=cmd){
            switch(lastcmd){
            case '+':
            case '-':
            case '>':
            case '<':
                putnest(fout,nest);
                fprintf(fout,"%s%c",lastcmd&0x10?"ptr":"(*ptr)",lastcmd&0x02?'+':'-');
                if(count==1)
                    fprintf(fout,"%c;\n",lastcmd&0x02?'+':'-');
                else
                    fprintf(fout,"=%d;\n",count);
                count=0;
                break;
            }
        }
        switch(cmd){
            case '+':
            case '-':
            case '>':
            case '<':
                count++;
                break;
            case '[':
                putnest(fout,nest);
                fprintf(fout,"while(*ptr){\n");
                nest++;
                break;
            case ']':
                nest--;
                putnest(fout,nest);
                fprintf(fout,"}\n");
                break;
            case '.':
                putnest(fout,nest);
                fprintf(fout,"putchar(*ptr);\n");
                break;
            case ',':
                putnest(fout,nest);
                fprintf(fout,"*ptr=getchar();\n");
                break;
        }
    }while(cmd!=EOF);
    fprintf(fout,"    return 0;\n");
    fprintf(fout,"}\n");
    fprintf(fout,"\n");
    return 0;
}

メモリとポインタを用意しておいて,あとは命令を1つずつRubyに置き換えるだけ。他の人と々アプローチだけど,いくらかわかりやすく書いたつもり。

書いてて気がついたけど,BFのループって終了判定が2カ所(前後)にあるんだな。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
print <<"EOBF"
class BF
  def initialize
    @memory = [0]
    @pointer = 0
  end
  def current
    @memory[@pointer]
  end
  def incr
    @memory[@pointer] += 1
  end
  def decr
    @memory[@pointer] -= 1
  end
  def right
    @pointer += 1
    unless @memory[@pointer]
      @memory << 0
    end
  end
  def left
    @pointer -= 1
  end
  def set(c)
    @memory[@pointer] = c
  end
  def printc
    print @memory[@pointer].chr
  end
end

bf = BF.new

EOBF

commands = {
  ">" => "bf.right",
  "<" => "bf.left",
  "+" => "bf.incr",
  "-" => "bf.decr",
  "." => "bf.printc",
  "," => "c = STDIN.getc; bf.set(c)",
  "[" => "begin; break if bf.current.zero?",
  "]" => "end until bf.current.zero?"
}
ARGF.each_byte do |c|
  cmd = commands[c.chr]
  puts cmd if cmd
end

なんか弄ってたら得体の知れないものに……w
1
2
3
4
5
6
7
using System;
using System.IO;
static class BFCompiler {
    public static void Main(String[] args) {
        Console.WriteLine(0 == args.Length || !File.Exists(args[0]) ? "usage: bfc [sourcefile]" : "using System;static class BF{static void Main(){byte[]m=new byte[256];int p=0;" + new StreamReader(args[0]).ReadToEnd().Replace("]","}").Replace("[","while(m[p]!=0){").Replace(".","Console.Write((char)m[p]);").Replace(",","m[p]=(byte)Console.Read();").Replace("+","m[p]++;").Replace("-","m[p]--;").Replace(">","p++;").Replace("<","p--;") + "}}");
    }
}

うーんと、]が条件判断を行わなわずに[に無条件ジャンプしても[が判断して]の後にジャンプするので同じということでしょう。


にしおさんのlex/yacc見てなるほど思ったので、本家lexのお勉強。 下のコードをbf.lとすると % lex bf.l % cc lex.yy.c -ll -o bfc としてコンパイル。 % ./bfc <hello.bf >hello.c % cc hello.c % ./a.out Hello World! のように使います。

一応 ++++++ → *pt = 6 みたいなことするようにしてみました。

System Message: WARNING/2 (<string>, line 12); backlink

Inline emphasis start-string without end-string.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
%{
#include <stdio.h>
#include <string.h>
%}
%option noyywrap
%%
\++ printf("*pt+=%d;\n", strlen(yytext));
\-+ printf("*pt-=%d;\n", strlen(yytext));
\>+ printf("pt+=%d;\n", strlen(yytext));
\<+ printf("pt-=%d;\n", strlen(yytext));
\, puts("*pt=getchar();");
\. puts("putchar(*pt);");
\[ puts("while(*pt){");
\] puts("}");
. ;
%%
int main(int argc, char **argv)
{
  puts("#include <stdio.h>\n"
  "int mem[30000];\n"
  "int *pt = mem;\n"
  "int main(){");
  yylex();
  puts("return 0;\n}");
  return 0;
}

無理矢理ですけど健全なマクロで解いてみました。

なお、出力されたコードもbf.scmを参照するのでロードパスを適切に設定しておく必要があります。

;;; bf-test.scm (サンプル)

(use bf)

(bf-compiler "+++++++++[>++++++++>+++++++++++>+++++<<<-]>.>++.+++++++..+++.>-.------------.<++++++++.--------.+++.------.--------.>+." 32)

;;; gosh bf-test.scm | gosh で"Hello, world!"が出力されます

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
;;; bf.scm
(define-module bf
  (export-all))

(define-syntax %bf-compiler
  (syntax-rules (< > + - |.| |,|)
    ((_ proc)
     proc)
    ((_ proc < rest ...)
     (%bf-compiler (compose (lambda (tape)
                              (cons (cons (cadr tape) (car tape))
                                    (cddr tape)))
                            proc)
                   rest ...))
    ((_ proc > rest ...)
     (%bf-compiler (compose (lambda (tape)
                              (cons (cdar tape)
                                    (cons (caar tape) (cdr tape))))
                            proc)
                   rest ...))
    ((_ proc + rest ...)
     (%bf-compiler (compose (lambda (tape)
                              (cons (car tape) (cons (modulo (+ (cadr tape) 1) 256)
                                                     (cddr tape))))
                            proc)
                   rest ...))
    ((_ proc - rest ...)
     (%bf-compiler (compose (lambda (tape)
                              (cons (car tape) (cons (modulo (- (cadr tape) 1) 256)
                                                     (cddr tape))))
                            proc)
                   rest ...))
    ((_ proc |.| rest ...)
     (%bf-compiler (compose (lambda (tape)
                              (write-byte (cadr tape))
                              (flush)
                              tape)
                            proc)
                   rest ...))     
    ((_ proc |,| rest ...)
     (%bf-compiler (compose (lambda (tape)
                              (let1 c (read-byte)
                              (cons (car tape)
                                    (cons (if (eof-object? c) 0 c) (cddr tape)))))
                            proc)
                   rest ...))                   
    ((_ proc [body ...] rest ...)
     (%bf-compiler (compose (lambda (tape)
                              (if (eq? (cadr tape) 0)
                                  tape
                                  (let1 f (%bf-compiler values body ...)
                                    (let loop ((t (f tape)))
                                      (if (eq? (cadr t) 0)
                                          t
                                          (loop (f t)))))))
                            proc)
                   rest ...))))

(define (bf-compiler str n)
  (write '(use bf))
  (write `((%bf-compiler values ,@(with-input-from-string
                              (regexp-replace-all #/[.,+-<>]/ #`"(,str)" "|\\0|")
                            read))
           ',(tape n))))
                              
(define (tape n)
  (let1 t (make-list n 0)
    (cons t t)))

(provide "bf")

あっ、tapeの実装がちょっと変だった。前のやつだと<でポインタが戻りすぎたときに変な挙動になります(修正版ではエラーになるはず)。

まあ、変なプログラムを書かなければさっきのでも問題ないはずです。

1
2
3
(define (tape n)
  (let1 t (make-list n 0)
    (cons '() t)))

ぐぁ、ポインタの進む向き逆にしてた。コードをあげなおします。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
;;; bf.scm
(define-module bf
  (export-all))

(define-syntax %bf-compiler
  (syntax-rules (< > + - |.| |,|)
    ((_ proc)
     proc)
    ((_ proc > rest ...)
     (%bf-compiler (compose (lambda (tape)
                              (cons (cons (cadr tape) (car tape))
                                    (cddr tape)))
                            proc)
                   rest ...))
    ((_ proc < rest ...)
     (%bf-compiler (compose (lambda (tape)
                              (cons (cdar tape)
                                    (cons (caar tape) (cdr tape))))
                            proc)
                   rest ...))
    ((_ proc + rest ...)
     (%bf-compiler (compose (lambda (tape)
                              (cons (car tape) (cons (modulo (+ (cadr tape) 1) 256)
                                                     (cddr tape))))
                            proc)
                   rest ...))
    ((_ proc - rest ...)
     (%bf-compiler (compose (lambda (tape)
                              (cons (car tape) (cons (modulo (- (cadr tape) 1) 256)
                                                     (cddr tape))))
                            proc)
                   rest ...))
    ((_ proc |.| rest ...)
     (%bf-compiler (compose (lambda (tape)
                              (write-byte (cadr tape))
                              (flush)
                              tape)
                            proc)
                   rest ...))     
    ((_ proc |,| rest ...)
     (%bf-compiler (compose (lambda (tape)
                              (let1 c (read-byte)
                              (cons (car tape)
                                    (cons (if (eof-object? c) 0 c) (cddr tape)))))
                            proc)
                   rest ...))                   
    ((_ proc [body ...] rest ...)
     (%bf-compiler (compose (lambda (tape)
                              (if (eq? (cadr tape) 0)
                                  tape
                                  (let1 f (%bf-compiler values body ...)
                                    (let loop ((t (f tape)))
                                      (if (eq? (cadr t) 0)
                                          t
                                          (loop (f t)))))))
                            proc)
                   rest ...))))

(define (bf-compiler str n)
  (write '(use bf))
  (write `((%bf-compiler values ,@(with-input-from-string
                              (regexp-replace-all #/[.,+-<>]/ #`"(,str)" "|\\0|")
                            read))
           ',(tape n))))
                              
(define (tape n)
  (let1 t (make-list n 0)
    (cons '() t)))

(provide "bf")

バグ発見。ランタイムテンプレート

コメント内のファイル名
  bfrts.templ → bfrts.tmpl

main の定義の右辺
  execute → run

高階関数を使ってみたい年頃なので高階関数使った版findpc を書いてみました。

findpc はループがネストしているとき '[' が来るごとに何度も呼ばれるのが
ちょっともったいない気もします。

それと hClose oh しないとバッファが flush されませんでした。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
-- import Maybe
-- import Control.Monad

-- -- where pc = findpc [] cs
-- where pc = findpc cs

findpc cs = fromMaybe (error "syntax error")
  $ msum $ snd $ mapAccumL f 0 cs  where
    f 0 (p,']') = (0, Just (p+1))
    f s (_,c) = (s+d, Nothing) where
      d = case c of; ']' -> (-1); '[' -> 1; _ -> 0

なでしこへ変換。1.50047以降用。出力されるコードはもはや日本語でもなんでもない(^ ^;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
!変数宣言が必要
FNとは文字列
BFCODEとは文字列
NAKOとは文字列=`mとは配列 
pとは整数=0
//コード開始
`
INDとは文字列=""
TABとは整数=0

FN="bf"の""でファイル選択
もし(FN="")ならば、おわり
BFCODE=FNを開く
BFCODEの改行を""に置換して文字列分解
反復
    対象で条件分岐
        ">"ならば
            NAKO=NAKO&IND&`p=p+1`&改行
        "<"ならば
            NAKO=NAKO&IND&`p=p-1`&改行
        "+"ならば
            NAKO=NAKO&IND&`m[p]=m[p]+1`&改行
        "-"ならば
            NAKO=NAKO&IND&`m[p]=m[p]-1`&改行
        "."ならば
            NAKO=NAKO&IND&`継続表示(CHR(m[p]))`&改行
        ","ならば
            NAKO=NAKO&IND&`m[p]=INT("${HEXエンコード(文字抜出(入力(空),1,1))}")`&改行
        "["ならば
            NAKO=NAKO&IND&`(m[p]<>0)の間`&改行
            TAB=TAB+1
            IND=リフレイン(タブ,TAB)
        "]"ならば
            TAB=TAB-1
            IND=リフレイン(タブ,TAB)
「nako」のファイル名抽出(FN)&「.nako」で保存ファイル選択
NAKOをそれに保存
おわり

素朴な実装。
一応、連続したインクリメント・デクリメントは一つにまとめる程度の最適化はしてます。
サンプルコードは弾さんのページからコピペしました。

% cat hello.bf
++++++++++[>+++++++>++++++++++>+++>+<<<<-]
>++.>+.+++++++..+++.>++.<<+++++++++++++++.
>.+++.------.--------.>+.>.

% ./bf.byte hello.bf
% cat hello.ml
(* runtime *)
let p = ref 0
let buf_size = 256
let make_buf () = Array.make buf_size 0
let buf = ref (make_buf ())
let extend () = buf := Array.append !buf (make_buf ())
let pincr n = p := !p + n; if Array.length !buf >= !p then extend ()
let pdecr n = p := !p - n; if !p < 0 then failwith "invalid pointer address!"
type vtype = INCR | DECR
let vset v n = !buf.(!p) <- !buf.(!p) + (match v with INCR -> n | DECR -> -n)
let output () = print_char (char_of_int !buf.(!p))
let input () = !buf.(!p) <- int_of_char (input_char stdin)
(* end of runtime *)
let () =
(* begin of code *)
vset INCR 10;
while !buf.(!p) <> 0 do
pincr 1;
vset INCR 7;
pincr 1;
vset INCR 10;
pincr 1;
vset INCR 3;
pincr 1;
vset INCR 1;
pdecr 4;
vset DECR 1;
done;
pincr 1;
vset INCR 2;
output ();
pincr 1;
vset INCR 1;
output ();
vset INCR 7;
output ();
vset INCR 3;
output ();
pincr 1;
vset INCR 2;
output ();
pdecr 2;
vset INCR 15;
output ();
pincr 1;
output ();
vset INCR 3;
output ();
vset DECR 6;
output ();
vset DECR 8;
output ();
pincr 1;
vset INCR 1;
output ();
pincr 1;
output ();
(* end of code *)
()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
(* lexer.mll *)
{
exception Eof
type instruction =
| PINCR of int | PDECR of int
| VINCR of int | VDECR of int
| OUTPUT | INPUT | FJUMP | BJUMP
}

rule token = parse
   | ('>' +) as s { PINCR (String.length s) }
   | ('<' +) as s { PDECR (String.length s) }
   | ('+' +) as s { VINCR (String.length s) }
   | ('-' +) as s { VDECR (String.length s) }
   | ('.' +)      { OUTPUT }
   | (',' +)      { INPUT }
   | ('[' +)      { FJUMP }
   | (']' +)      { BJUMP }
   | ['\n' '\r']  { token lexbuf }
   | eof          { raise Eof }

(* bf.ml *)
let runtime = "\
(* runtime *)
let p = ref 0
let buf_size = 256
let make_buf () = Array.make buf_size 0
let buf = ref (make_buf ())
let extend () = buf := Array.append !buf (make_buf ())
let pincr n = p := !p + n; if Array.length !buf >= !p then extend ()
let pdecr n = p := !p - n; if !p < 0 then failwith \"invalid pointer address!\"
type vtype = INCR | DECR
let vset v n = !buf.(!p) <- !buf.(!p) + (match v with INCR -> n | DECR -> -n)
let output () = print_char (char_of_int !buf.(!p))
let input () = !buf.(!p) <- int_of_char (input_char stdin)
(* end of runtime *)
"
let code_begin = "\
let () =
(* begin of code *)
"
let code_end = "\
(* end of code *)
()
"

open Lexer

let code_of_instruction token =
   let to_s = string_of_int in
   match token with
   | PINCR i -> "pincr " ^ (to_s i) ^ ";\n"
   | PDECR i -> "pdecr " ^ (to_s i) ^ ";\n"
   | VINCR i -> "vset INCR " ^ (to_s i) ^ ";\n"
   | VDECR i -> "vset DECR " ^ (to_s i) ^ ";\n"
   | OUTPUT  -> "output ();\n"
   | INPUT   -> "input ();\n"
   | FJUMP   -> "while !buf.(!p) <> 0 do\n"
   | BJUMP   -> "done;\n"

let translate str =
   let buf = Buffer.create ((String.length runtime) * 2) in
   let add = Buffer.add_string buf in
   let ()  = add runtime; add code_begin in
   let lexbuf = Lexing.from_string str in
   try
      while true do
         add (code_of_instruction (Lexer.token lexbuf))
      done;
      failwith "unreached"
   with Lexer.Eof ->
      add code_end;
      Buffer.contents buf

let main () =
   match Sys.argv with
   | [|_; fname |] -> begin
        let out_file_name =
           try (Filename.chop_extension fname) ^ ".ml"
           with _ -> fname ^ ".ml"
        in
        let in_ch = open_in fname in
        let buf = Buffer.create 10 in
        try
           while true do
              Buffer.add_string buf (input_line in_ch)
           done
        with End_of_file ->
           close_in in_ch;
           let code = translate (Buffer.contents buf) in
           let out_ch = open_out out_file_name in
           output_string out_ch code;
           close_out out_ch
     end
   | _ ->
        print_endline ("usage: " ^ Sys.argv.(0) ^ " BF_file")
let () = if not !Sys.interactive then main ()

1
2
3
4
名前が付いてたんですかっっ。
でも上のは、スタックオーバーフローじゃなくて単に
Out of rangeなんだろうかとふらふら考えてた跡です。
すみません。

ありがとうございます。ぴったしな関数があったんですね。
Pervasivesなんて聞き慣れないモジュール名だったので
リファレンスも見逃してました。基本だったのに……

一番手間がかかったのはgetcharの実装です。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
begingroupundo;
gofiletop;
$code['>'] = "#ptr=#ptr+1;";
$code['<'] = "#ptr=#ptr-1;";
$code['+'] = "#buf[#ptr]=#buf[#ptr]+1;";
$code['-'] = "#buf[#ptr]=#buf[#ptr]-1;";
$code['.'] = "insert char(#buf[#ptr]);";
$code[','] = "call get;";
$code['['] = "while(#buf[#ptr]){";
$code[']'] = "}";
#isUsedGetchar = false;
while( code != eof ) {
    if( code == ',' ) #isUsedGetchar = true;
    insert $code[code];
    delete;
}
if( #isUsedGetchar ) {
    insert "endmacro;get:if($get==\"\"&&#c<=0)$get=input(\"入力\");#c=ascii($get);if($get==\"\")#c=-1;$get=rightstr($get,strlen($get)-strlen(char(#c)));#buf[#ptr]=#c;return;";
}
endgroupundo;

Mac OS X (PowerPC 32bit) アセンブリで。コマンドラインでBFのソースを書いて、標準出力にアセンブリを出力します。当方ヘタレなもんで、Mac OS X PowerPCアセンブリでのファイルの扱いがよくわかりません。

# BFコンパイラのコンパイル
% gcc -o bfc-osxppc bfc-osxppc.s
% ./bfc-osxppc "++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+." > hello-bf.s

# 出力されたソースのコンパイルと実行
% gcc -o hello-bf hello-bf.s
% ./hello-bf
Hello World!
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
;; % gcc -o bfc-osxppc bfc-osxppc.s
;; 
;; Usage: ./bfc-osxppc "source..." > output
;;
;; ex)
;; % ./bfc-osxppc "++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+." > hello-bf.s
;; % gcc -o hello-bf hello-bf.s
;; % ./hello-bf
;; Hello World!

        .machine ppc
        .globl _main
        
        .macro  PUSH
                stwu    r3, -4(r1)
                stwu    r4, -4(r1)
                stwu    r5, -4(r1)
                stwu    r6, -4(r1)
                stwu    r7, -4(r1)
                stwu    r8, -4(r1)
                stwu    r9, -4(r1)
                stwu    r10, -4(r1)
                stwu    r11, -4(r1)
                stwu    r12, -4(r1)
                stwu    r13, -4(r1)
                mflr    r20
                stwu    r20, -4(r1)
        .endmacro

        .macro POP
                lwz     r20, 0(r1)
                mtlr    r20
                lwzu    r13, 4(r1)
                lwzu    r12, 4(r1)
                lwzu    r11, 4(r1)
                lwzu    r10, 4(r1)
                lwzu    r9, 4(r1)
                lwzu    r8, 4(r1)
                lwzu    r7, 4(r1)
                lwzu    r6, 4(r1)
                lwzu    r5, 4(r1)
                lwzu    r4, 4(r1)
                lwzu    r3, 4(r1)
                addi    r1, r1, 4
        .endmacro

        .macro PRINT
                PUSH
                li      r3, 1
                lis     r4, hi16($0)
                addi    r4, r4, lo16($0)
                lis     r5, hi16($0_len)
                addi    r5, r5, lo16($0_len)
                li      r0, 4
                sc
                POP
        .endmacro

        .macro PRINT_DIGIT
                PUSH
                mr      r3, $0
                bl      print_digit
                POP
        .endmacro

;; r7:  ソースコード
;; r8:  読み込んだ文字
;; r9:  配列のサイズ
;; r10: ラベルの数
_main:
        lwz     r7, 4(r4)       ; argv[1]
        li      r8, 0
        li      r9, 10
        li      r10, 0

;; ヘッダを出力
print_header:
        PRINT   bf_header

compile:
        lbz     r8, 0(r7)
        cmpli   cr7, r8, 0
        beq     cr7, finish_compile
        addi    r7, r7, 1

        cmpli   cr7, r8, 62     ; '>'
        beq     cr7, compile_inc
        cmpli   cr7, r8, 60     ; '<'
        beq     cr7, compile_dec
        cmpli   cr7, r8, 43     ; '+'
        beq     cr7, compile_plus
        cmpli   cr7, r8, 45     ; '-'
        beq     cr7, compile_minus
        cmpli   cr7, r8, 46     ; '.'
        beq     cr7, compile_output
        cmpli   cr7, r8, 44     ; ','
        beq     cr7, compile_input
        cmpli   cr7, r8, 91     ; '['
        beq     cr7, compile_forward
        cmpli   cr7, r8, 93     ; ']'
        beq     cr7, compile_backward

        ;; 他の文字は無視
        b       compile

compile_inc:
        addi    r9, r9, 1
        PRINT   bf_inc
        b       compile

compile_dec:
        subi    r9, r9, 1
        PRINT   bf_dec
        b       compile

compile_plus:
        PRINT   bf_plus
        b       compile

compile_minus:
        PRINT   bf_minus
        b       compile

compile_output:
        PRINT   bf_output
        b       compile

compile_input:
        PRINT   bf_input
        b       compile

compile_forward:
        addi    r10, r10, 1
        stwu    r10, -4(r1)
        PRINT   bf_forward_cmp
        PRINT   bf_end_label
        PRINT_DIGIT     r10
        PRINT   lf
        PRINT   bf_begin_label
        PRINT_DIGIT     r10
        PRINT   bf_label_term
        b       compile

compile_backward:
        lwz     r2, 0(r1)
        addi    r1, r1, 4
        PRINT   bf_backward_cmp
        PRINT   bf_begin_label
        PRINT_DIGIT     r2
        PRINT   lf
        PRINT   bf_end_label
        PRINT_DIGIT     r2
        PRINT   bf_label_term
        b       compile

finish_compile:
        PRINT   lf
        PRINT   bf_exit
        PRINT   bf_data
        PRINT   bf_array_decl

print_array:
        PRINT   bf_array_element
        cmpli   cr7, r9, 0
        subi    r9, r9, 1
        bgt     cr7, print_array
        PRINT   bf_array_decl_end
        
exit:
        PRINT   lf
        
        ;; sys_exit()
        li      r3, 0
        li      r0, 1           ; sys_exit
        sc


;; 正の整数を10進数で出力
;; r3: 出力する整数
print_digit:
        mr      r10, r3
        li      r11, 25000      ; fig, 桁
        mulli   r11, r11, 4
        li      r14, 10         ; 桁の除算用
        li      r15, 0          ; 残りの数字をすべて表示するか

;; 上の桁から順に出力する
print_each_digit:
        divw    r12, r10, r11   ; d = i / fig
        mullw   r13, r11, r12   ; rem = i - fig * d
        sub     r10, r10, r13

        cmpli   cr7, r15, 0
        cmpli   cr6, r12, 0     ; d == 0
        cmpli   cr5, r11, 1     ; fig > 1
        crand   2, 30, 26       ; cr0[eq] = cr7[eq] && cr6[eq]
        crand   2, 2, 21        ; cr0[eq] = cr0[eq] && cr5[gt]
        divw    r11, r11, r14   ; fig /= 10
        beq     cr0, print_each_digit

        ;; 出力
        li      r15, 1
        addi    r12, r12, 48    ; ASCIIコードにする
        li      r3, 1           ; 標準出力
        lis     r4, hi16(temp)
        addi    r4, r4, lo16(temp)
        li      r5, 1
        li      r0, 4           ; sys_write
        stb     r12, 0(r4)
        PUSH
        sc
        POP

        cmpli   cr7, r11, 0     ; fig == 0 なら終了
        beqlr   cr7
        b       print_each_digit


;; 定数

        .data
        .align  4

temp:
        .asciz  "    "
        .align  4

lf:
        .asciz  "\n"
        .align  4
        lf_len = 1
        .align  4

bf_header:
        .asciz  ";; This file is automatically generated. Do not edit.\n\n    .machine ppc\n    .globl _main\n\n    .macro  PUSH\n            stwu    r7, -4(r1)\n            stwu    r8, -4(r1)\n            stwu    r9, -4(r1)\n    .endmacro\n\n    .macro  POP\n            lwz     r9, 0(r1)\n            lwzu    r8, 4(r1)\n            lwzu    r7, 4(r1)\n            addi    r1, r1, 4\n    .endmacro\n\n    .macro  OUTPUT\n            PUSH\n            li      r3, 1\n            lis     r4, hi16(temp)\n            addi    r4, r4, lo16(temp)\n            stb     r8, 0(r4)\n            li      r5, 1\n            li      r0, 4\n            sc\n            POP\n    .endmacro\n\n\n_main:\n    lis     r7, hi16(array)\n    addi    r7, r7, lo16(array)\n    li      r8, 0\n\n"
        bf_header_len = . - bf_header - 1
        .align  4

bf_exit:
        .asciz  "exit:\n    li      r8, 10\n    OUTPUT\n    li      r3, 0\n    li      r0, 1\n    sc\n\n"
        bf_exit_len = . - bf_exit - 1
        .align  4

bf_data:
        .asciz  "\n\n    .data\n    .align  4\n\ntemp:\n    .asciz  \"  \"\n    .align  4\n\n"
        bf_data_len = . - bf_data - 1
        .align  4

bf_array_decl:
        .asciz  "array:\n    .long "
        bf_array_decl_len = . - bf_array_decl - 1
        .align  4

bf_array_element:
        .asciz  "0, "
        bf_array_element_len = . - bf_array_element - 1
        .align  4

bf_array_decl_end:
        .asciz  "0\n    .align  4\n"
        bf_array_decl_end_len = . - bf_array_decl_end - 1
        .align  4

bf_inc:
        .asciz  "    addi    r7, r7, 4\n"
        bf_inc_len = . - bf_inc - 1
        .align  4

bf_dec:
        .asciz  "    subi    r7, r7, 4\n"
        bf_dec_len = . - bf_dec - 1
        .align  4

bf_plus:
        .asciz  "    lwz     r8, 0(r7)\n    addi    r8, r8, 1\n    stw     r8, 0(r7)\n"
        bf_plus_len = . - bf_plus - 1
        .align  4

bf_minus:
        .asciz  "    lwz     r8, 0(r7)\n    subi    r8, r8, 1\n    stw     r8, 0(r7)\n"
        bf_minus_len = . - bf_minus - 1
        .align  4

bf_output:
        .asciz  "    lwz     r8, 0(r7)\n    OUTPUT\n"
        bf_output_len = . - bf_output - 1
        .align  4

bf_input:
        .asciz  "    INPUT\n"
        bf_input_len = . - bf_input - 1
        .align  4

bf_forward_cmp:
        .asciz  "    lwz     r8, 0(r7)\n    cmpi    cr7, r8, 0\n    beq     cr7, "
        bf_forward_cmp_len = . - bf_forward_cmp - 1
        .align  4

bf_begin_label:
        .asciz  "begin"
        bf_begin_label_len = . - bf_begin_label - 1
        .align  4

bf_end_label:
        .asciz  "end"
        bf_end_label_len = . - bf_end_label - 1
        .align  4

bf_label_term:
        .asciz  ":\n"
        bf_label_term_len = . - bf_label_term - 1
        .align  4

bf_backward_cmp:
        .asciz  "    lwz     r8, 0(r7)\n    cmpi    cr7, r8, 0\n    bne     cr7, "
        bf_backward_cmp_len = . - bf_backward_cmp - 1
        .align  4

昔七行スレに投下したのを投稿してみる。
要gcc、出力はa.outで固定です。
$ gcc bfc.c -o bfc #コンパイラのコンパイル
$ ./bfc hoge.bf #コンパイル
$ ./a.out #実行
1
2
3
4
5
6
7
#include <stdio.h>
int system(),i;int main(int c,char**v){FILE*f=fopen(*++v,"r"),*g=fopen("!.c","w"
);char s[99],*o[]={"putchar(*p);","*p=getchar();","while(*p){","}","++p;","--p;"
,"++*p;","--*p;"};fputs("#include<stdio.h>\nint main(void){static int b[30000],"
"*p=b;",g);for(;f&&(c=fgetc(f))-EOF;)for(i=8;0<i--;)fputs(".,[]><+-"[i]-c?"":o[i
],g);fputs("return 0;}",g);fclose(f);fclose(g);sprintf(s,"gcc !.c -o%s",*++v);
return system(s);}

MetaOCaml 使ってみました。コード生成部が読みにくい……

出力されるコードはこんな風になります:

# compile "++++++++[>++++++++<-]>+.";;
- : ('a, state -> unit) code =
.<fun s_1 ->
   let _ = (s_1.array).(s_1.ptr) <- ((s_1.array).(s_1.ptr) + 8) in
   let _ =
    while ((s_1.array).(s_1.ptr) <> 0) do
     let _ = s_1.ptr <- ((s_1.ptr + 1) mod s_1.array_size) in
     let _ = (s_1.array).(s_1.ptr) <- ((s_1.array).(s_1.ptr) + 8) in
     let _ = s_1.ptr <- ((s_1.ptr + (-1)) mod s_1.array_size) in
     let _ = (s_1.array).(s_1.ptr) <- ((s_1.array).(s_1.ptr) + (-1)) in ()
    done in
   let _ = s_1.ptr <- ((s_1.ptr + 1) mod s_1.array_size) in
   let _ = (s_1.array).(s_1.ptr) <- ((s_1.array).(s_1.ptr) + 1) in
   let _ =
    (output_char stdout
      (((* cross-stage persistent value (as id: Char.chr) *))
        (s_1.array).(s_1.ptr))) in
   ()>.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
type inst = Inc of int | Shift of int | Input | Output | Loop of inst list

let count_while c s =
  let rec loop m =
    match Stream.peek s with
      | Some c' when c = c' -> Stream.junk s; loop (m+1)
      | _ -> m
  in loop 1

let rec parse1 s =
  match Stream.next s with
    | '+' -> Inc (count_while '+' s)
    | '-' -> Inc (- (count_while '-' s))
    | '>' -> Shift (count_while '>' s)
    | '<' -> Shift (- (count_while '<' s))
    | '.' -> Output
    | ',' -> Input
    | '[' -> Loop (parseloop s [])
    | _   -> parse1 s
and parse s acc =
  try let i = parse1 s in parse s (i :: acc)
  with Stream.Failure -> List.rev acc
and parseloop s acc =
  if Stream.peek s = Some ']' then (Stream.junk s; List.rev acc)
  else parseloop s (parse1 s :: acc)

type state = { mutable ptr : int; array : int array; array_size : int; }

let rec trans1 i s =
  match i with
    | Inc n   -> .< (.~s).array.((.~s).ptr) <- (.~s).array.((.~s).ptr) + n >.
    | Shift n -> .< (.~s).ptr <- ((.~s).ptr + n) mod (.~s).array_size >.
    | Output  -> .< output_char stdout (Char.chr (.~s).array.((.~s).ptr)) >.
    | Input   -> .< (.~s).array.((.~s).ptr) <- Char.code (input_char stdin) >.
    | Loop is ->
        .< while (.~s).array.((.~s).ptr) <> 0 do .~(trans is s) done >.
and trans insts s =
  let f i code = .< let _ = .~(trans1 i s) in .~code >.
  in List.fold_right f insts .<()>.

let make_state n = { ptr = 0; array = Array.create n 0; array_size = n; }
let compile str =
  .<fun s -> .~(trans (parse (Stream.of_string str) []) .<s>.)>.
let execute str = (.! compile str) (make_state 100)

VBAを使用せず、Excelの普通(?)の関数のみでBrainfuckインタプリタを作りました。

メモリセル(16個だけ)、プログラムの長さ(1024文字まで)、実行ステップ数(1024ステップまで)に制限があります。 あと、Excelで表示できない文字は出力されないようにしました。

1
2
3
ソースを貼り付けることができないので、
Excelファイルをここにアップロードしておきました。
http://unkun.ikaduchi.com/uploadFiles/BF.xls


たとえば以下のようにBFコードが生成されます。 インタプリタではなくて、コンパイル後のGroovyでのインストラクション表現をBFコード文字列と同じにしたということです。まあ内部DSL、BF DSLみたいなもんだ(厳密には違うけど)。

"++++++++++[>++++++++++<-]>++++.+++++++.--------.--.".run()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
println """
String.metaClass.define {
  jump { ip, direc, target, curr ->
         if (code[ip] == curr) {
           ip = jump(ip+direc+direc, direc, target, curr)
         }
         if (code[ip] == target) {
           return ip+direc
         }
         jump(ip+direc, direc, target, curr)
  }
  run {
    code = new String(delegate)
    for (int ip=0; ip>=0 && ip<delegate.size(); ip=delegate[ip].intern().exec(ip))
      ;
    System.out.flush()
  }
}
  
'>'.metaClass.exec={ ptr++; it+1 }
'<'.metaClass.exec={ ptr--; it+1 }
'+'.metaClass.exec={ data[ptr]++; it+1 }
'-'.metaClass.exec={ data[ptr]--; it+1 }
'.'.metaClass.exec={ System.out.write(data[ptr]); it+1 }
','.metaClass.exec={ System.in.read(data, ptr, 1); it+1 }
'['.metaClass.exec={ (data[ptr]==0 ? jump(it+1, +1, ']', delegate) : it+1) }
']'.metaClass.exec={ (data[ptr]!=0 ? jump(it-1, -1, '[', delegate) : it)+1 }

data = new byte[30000]
ptr = 0
"${args[0]}".run()
"""

System Message: WARNING/2 (<string>, line 2)

Title underline too short.

うう、言語指定をGroovyにするのを忘れたんで再投稿。
----

たとえば以下のようにBFコードが生成されます。 インタプリタではなくて、コンパイル後のGroovyでのインストラクション表現をBFコード文字列と同じにしたということです。まあ内部DSL、BF DSLみたいなもんだ(厳密には違うけど)。

"++++++++++[>++++++++++<-]>++++.+++++++.--------.--.".run()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
println """
String.metaClass.define {
  jump { ip, direc, target, curr ->
         if (code[ip] == curr) {
           ip = jump(ip+direc+direc, direc, target, curr)
         }
         if (code[ip] == target) {
           return ip+direc
         }
         jump(ip+direc, direc, target, curr)
  }
  run {
    code = new String(delegate)
    for (int ip=0; ip>=0 && ip<delegate.size(); ip=delegate[ip].intern().exec(ip))
      ;
    System.out.flush()
  }
}
  
'>'.metaClass.exec={ ptr++; it+1 }
'<'.metaClass.exec={ ptr--; it+1 }
'+'.metaClass.exec={ data[ptr]++; it+1 }
'-'.metaClass.exec={ data[ptr]--; it+1 }
'.'.metaClass.exec={ System.out.write(data[ptr]); it+1 }
','.metaClass.exec={ System.in.read(data, ptr, 1); it+1 }
'['.metaClass.exec={ (data[ptr]==0 ? jump(it+1, +1, ']', delegate) : it+1) }
']'.metaClass.exec={ (data[ptr]!=0 ? jump(it-1, -1, '[', delegate) : it)+1 }

data = new byte[30000]
ptr = 0
"${args[0]}".run()
"""

<a href=http://www.silka.org/>buy valium online</a>
The increased and prolonged sedation is identified to occur with cimetidine, ketoconazole, fluvoxamine, fluoxetine and omeprazole.

System Message: WARNING/2 (<string>, line 4)

Definition list ends without a blank line; unexpected unindent.

<a href=http://www.silka.org/>buy valium online</a>

In patients with myasthenia gravis who are prescribed with Valium, care should be taken on account of preexisting muscle weakness. http://www.silka.org/ - valium pill

System Message: ERROR/3 (<string>, line 8)

Unexpected indentation.
No prescription is needed!

System Message: WARNING/2 (<string>, line 9)

Block quote ends without a blank line; unexpected unindent.

If Valium is to be combined with other centrally-acting agents, for example, antipsychotics, anxiolytics/sedatives, antidepressants, hypnotics, anticonvulsants, narcotic analgesics, anesthetics and sedative antihistamines, it should be borne in mind that their effects may potentiate or be potentiated by the action of Valium.


Hi, <a href=http://nicheblogssquad.com/>Play Online Casino</a>

System Message: ERROR/3 (<string>, line 3)

Unexpected indentation.
These websites are absolutely legitimate and authorized by government.

System Message: WARNING/2 (<string>, line 4)

Block quote ends without a blank line; unexpected unindent.
<a href=http://nicheblogssquad.com/>Play Casino For Fun</a>
Online gaming is taking the industry by storm and people don't even require leaving their home to have some fun with gambling.
http://nicheblogssquad.com/ - Best Online Casinos
Some websites are freely accessible, while you'll require putting down a certain deposit in others.

На финансовом рынке страны Всеукраинский Акционерный Банк (<a href=http://www.express-release.com/organization/1417>VAB Банк</a>) работает с 1992 года и, согласно данным НБУ, входит в группу крупных финансовых учреждений. С октября 2006 года – в составе универсальной финансовой группы VAB Group. Региональная сеть Банка по состоянию на 1.03.2009 г. представлена 22 филиалами и 151 отделениями в Украине, а также представительством в г. Будапешт (Венгрия). В числе акционеров <a href=http://www.express-release.com/organization/1417>VAB Банк</a> – крупная международная компания TBIF Financial Services BV (Амстердам, Нидерланды). По состоянию на 1 января 2009 года объем чистых активов VAB Банка составил 7,7 млрд грн, регулятивный капитал Банка – 925 млн грн. <a href=http://www.express-release.com/organization/1417>VAB Банк</a> <a href=http://www.express-release.com/organization/1417>VAB Банк</a> http://www.express-release.com/organization/1417 - VAB Банк


Biden War On Drugs <a href=http://greenheartmedicaluniversity.com/>order acomplia online</a>

System Message: ERROR/3 (<string>, line 3)

Unexpected indentation.
Almost half of the volunteers who took part in the study showed improvement in HDL-cholesterol level beyond the expected increase resulting from weight loss.

System Message: WARNING/2 (<string>, line 4)

Block quote ends without a blank line; unexpected unindent.
<a href=http://greenheartmedicaluniversity.com/>buy acomplia on line</a>
In test periods, Acomplia is found to associate a few side effects among which vomiting tendency and nausea was most common.
http://greenheartmedicaluniversity.com/ - buy acomplia
It was initially launched in UK and then gradually launched in Denmark, Ireland, Germany, Finland and Norway in the last 6 months of that year.

Better Medicine Canada fartonast <a href=http://www.jo3design.com/>Vardenafil Online</a>

System Message: ERROR/3 (<string>, line 3)

Unexpected indentation.
If a patient has an allergy to the drug content of Levitra, this drug is not recommended for him.

System Message: WARNING/2 (<string>, line 4)

Block quote ends without a blank line; unexpected unindent.
<a href=http://www.jo3design.com/>Buy Levitra On Line</a>
Levitra loosens the muscles around the penile area of the male body allowing blood to flow into the penis at increased rates.
http://www.jo3design.com/ - Levitr