BrainCrash in SQL
Brainf*ck in SQL を改造して、BrainCrash in SQL を書いてみました!
BrainCrash については 404 Not Found を参考にしてください。
WITH -- 入力 Input(id, bc_program, stdin) AS ( -- Hello, World!と標準出力に出力するプログラム SELECT 0, '', '' -- OR計算 UNION ALL SELECT 1, '|', '' -- OR計算2 UNION ALL SELECT 2, '|<', '' -- AND計算 UNION ALL SELECT 3, '&', '' -- AND計算2 UNION ALL SELECT 4, '&<', '' -- NOT計算 UNION ALL SELECT 5, '[>]++++++++++[-<<<<<<<<<<<<<+++++++++++>>>>>>>>>>>>>]<<<<<<<<<<<<<+~', '' -- XOR計算 UNION ALL SELECT 6, '^', '' -- XOR計算2 UNION ALL SELECT 7, '^<', '' -- 入力された文字+world! UNION ALL SELECT 8, ',+[-.,+]>>>>>>', 'Hoge' -- worldのwを大文字にする UNION ALL SELECT 9, '>>>>>>^<<<<<<<', '' ) , Input_(id, bc_program, stdin) AS ( SELECT id, bc_program + '[.>]', stdin + NCHAR(255) FROM Input ) , BracketTableSrc(id, open_bracket_pos, close_bracket_pos, stack, src, crnt) AS ( -- 括弧の対応表の元データ -- スタックに開き括弧のインデックスをpushし、 -- 閉じカッコを見つけ次第そのインデックスとスタックに積まれているインデックスを取り出して、 -- open_bracket_posとclose_bracket_posに格納する。 -- 後戻りが発生しないので、文字列消費型(srcがどんどん減っていく)で構成。 SELECT id , 0 , 0 , CAST('' AS nvarchar(max)) , bc_program , 1 FROM Input_ UNION ALL SELECT id -- スタックのtopにあるインデックスを設定 , CASE LEFT(src, 1) WHEN ']' THEN CAST(LEFT(stack, CHARINDEX(' ', stack, 1)) AS int) ELSE 0 END -- 閉じカッコのインデックスを設定 , CASE LEFT(src, 1) WHEN ']' THEN crnt ELSE 0 END -- 開き括弧ならスタックにインデックスをpushし、 -- 閉じカッコならスタックからpopする , CASE LEFT(src, 1) WHEN '[' THEN CAST(crnt AS nvarchar(4)) + ' ' + stack WHEN ']' THEN SUBSTRING(stack, CHARINDEX(' ', stack, 1) + 1, LEN(stack)) ELSE stack END -- 消費した文字列を取り除いたものが次のsrcになる , SUBSTRING(src, 2, LEN(src)) , crnt + 1 FROM BracketTableSrc WHERE src <> '' ) , BracketTable(id, open_bracket_pos, close_bracket_pos) AS ( -- open_bracket_posとclose_bracket_posがどちらも格納されているデータのみ抜き出す -- このテーブルをひくことで対応括弧のインデックスが取得できる SELECT id , open_bracket_pos , close_bracket_pos FROM BracketTableSrc WHERE open_bracket_pos <> 0 AND close_bracket_pos <> 0 ) , Eval(id, array, ptr, input_pgm, crnt, stdout, stdin) AS ( -- BrainCrashの評価エンジン -- ループが存在するので、文字列消費型ではなく、 -- オリジナル文字列とインデックスによる方法(input_pgmは変わらず、crntが変わる)を使用している。 SELECT id -- BrainCrashが使用する配列は2000に制限 -- SQL Serverに限らず、一行のサイズが制限されていることは多いので一応。 -- 初期状態はHello, world!の後が全て0 , CAST('Hello, world!' + REPLICATE(NCHAR(0), 2000 - LEN('Hello, world!')) AS nchar(2000)) , 1 -- ポインタの初期状態はSQLの文字列のインデックスが1からなので1 , bc_program , 1 , CAST('' AS nvarchar(max)) -- 標準出力は最初空 , stdin -- 標準出力はInputのものをそのまま使う FROM Input_ UNION ALL SELECT id , CAST( -- 現在見ている文字が+、-、|、&、^、~、,のいずれかなら配列を操作する CASE SUBSTRING(input_pgm, crnt, 1) WHEN '+' THEN CASE UNICODE(SUBSTRING(array, ptr, 1)) WHEN 255 THEN STUFF(array, ptr, 1, NCHAR(0)) ELSE STUFF(array, ptr, 1, NCHAR(UNICODE(SUBSTRING(array, ptr, 1)) + 1)) END WHEN '-' THEN CASE UNICODE(SUBSTRING(array, ptr, 1)) WHEN 0 THEN STUFF(array, ptr, 1, NCHAR(255)) ELSE STUFF(array, ptr, 1, NCHAR(UNICODE(SUBSTRING(array, ptr, 1)) - 1)) END WHEN '|' THEN STUFF( array, ptr + 1, 1, NCHAR( UNICODE(SUBSTRING(array, ptr, 1)) | UNICODE(SUBSTRING(array, ptr + 1, 1)) ) ) WHEN '&' THEN STUFF( array, ptr + 1, 1, NCHAR( UNICODE(SUBSTRING(array, ptr, 1)) & UNICODE(SUBSTRING(array, ptr + 1, 1)) ) ) WHEN '^' THEN STUFF( array, ptr + 1, 1, NCHAR( UNICODE(SUBSTRING(array, ptr, 1)) ^ UNICODE(SUBSTRING(array, ptr + 1, 1)) ) ) WHEN '~' THEN STUFF(array, ptr, 1, NCHAR(~CAST(UNICODE(SUBSTRING(array, ptr, 1)) AS tinyint))) -- 標準入力にも一応対応。標準入力を一番最初に与えておかなければいけないのはSQLの限界ですね WHEN ',' THEN STUFF(array, ptr, 1, LEFT(stdin, 1)) ELSE array END AS nchar(2000)) -- 現在見ている文字が>か<か|か&か^ならポインタを操作する , CASE WHEN SUBSTRING(input_pgm, crnt, 1) IN ('>', '|', '&', '^') THEN ptr + 1 WHEN SUBSTRING(input_pgm, crnt, 1) = '<' AND ptr <> 1 THEN ptr - 1 ELSE ptr END , input_pgm -- 現在見ている文字が[か]ならインデックスを操作する , CASE SUBSTRING(input_pgm, crnt, 1) WHEN '[' THEN CASE UNICODE(SUBSTRING(array, ptr, 1)) WHEN 0 THEN (SELECT close_bracket_pos FROM BracketTable WHERE Eval.id = BracketTable.id AND open_bracket_pos = crnt) + 1 ELSE crnt + 1 END WHEN ']' THEN (SELECT open_bracket_pos FROM BracketTable WHERE Eval.id = BracketTable.id AND close_bracket_pos = crnt) ELSE crnt + 1 END -- 現在見ている文字が.なら標準出力に追記する , CASE SUBSTRING(input_pgm, crnt, 1) WHEN '.' THEN stdout + SUBSTRING(array, ptr, 1) ELSE stdout END -- 現在見ている文字が,なら標準入力から消費する , CASE SUBSTRING(input_pgm, crnt, 1) WHEN ',' THEN SUBSTRING(stdin, 2, LEN(stdin)) ELSE stdin END FROM Eval WHERE crnt <= LEN(input_pgm) ) , Result(id, stdout) AS ( SELECT id , stdout FROM Eval P WHERE crnt = (SELECT MAX(crnt) FROM Eval C WHERE P.id = C.id) ) SELECT * FROM Result ORDER BY id OPTION (MAXRECURSION 32767)
実行結果はこんな感じ。
id | stdout |
---|---|
0 | Hello, world! |
1 | mllo, world! |
2 | Hmllo, world! |
3 | @llo, world! |
4 | H@llo, world! |
5 | Hello, world! |
6 | -llo, world! |
7 | H-llo, world! |
8 | Hoge world! |
9 | Hello, World! |
実はこれ組んでるときに Brainf*ck in SQL のバグを見つけてしまった・・・後で直します!