constexpr で Brainfuck

とりあえず、Sprout を使って書いてみました。
Sprout まじ Sprout。
あんまり効率とかは考えないでベタが出来ですが、入力から出力までをコンパイル時に行います。
そういえば constexpr で Brainfuck の実装は見かけたことがないような?

[ソース]

#include <sprout/string.hpp>
#include <sprout/array.hpp>
#include <sprout/iterator.hpp>
#include <sprout/algorithm/fill.hpp>
#include <sprout/operation.hpp>
#include <sprout/range/algorithm/count.hpp>
#include <boost/preprocessor/repetition/enum.hpp>
#include <sprout/operation/set.hpp>


#ifndef SOURCE_FILE
#    define SOURCE_FILE "brainfuck.txt"
#endif


//
//    static print
//
template<std::size_t Index, typename String>
constexpr char
at_c(String const& str){
    return Index >= str.length()
         ? char('\0')
         : str[Index];
}

template<char... cs>
struct chars{};

#define STRING_MAX_LENGTH    16

#define DECL_AT_C(z, n, text)    \
    at_c<n>(text)

#define TO_STRING_TYPE(str)    \
    chars<BOOST_PP_ENUM(STRING_MAX_LENGTH, DECL_AT_C, str)>

#include <boost/mpl/print.hpp>

template<typename String>
void
static_print(){
    namespace m =boost::mpl;
    typedef typename m::print<String>::type output;
}


//
//    stringstream
//
template<std::size_t Size, typename String = sprout::string<Size>>
struct basic_stringstream{
    constexpr String const&
    str() const{
        return string;
    }

    String string;
};


typedef basic_stringstream<0> stringstream;

template<std::size_t Lim>
using stringstream_lim = basic_stringstream<Lim>;


template<
    std::size_t N, typename String,
    typename Output,
    typename Result = basic_stringstream<N, String>
>
constexpr Result
output(basic_stringstream<N, String> const& ss, Output const& output){
    return { sprout::realign_to<String>(ss.string + output) };
}


template<typename String, typename Output>
constexpr auto
output(basic_stringstream<0, String> const& ss, Output const& output)
->basic_stringstream<0, decltype(String{} + output)>{
    return basic_stringstream<0, decltype(String{} + output)>{ ss.str() + output };
}


template<std::size_t Size, typename String, typename T, std::size_t N>
constexpr auto
operator <<(basic_stringstream<Size, String> const& ss, T const(&str)[N])
->decltype(output(ss, sprout::to_string(str))){
    return output(ss, sprout::to_string(str));
}

template<
    std::size_t Size, typename String, typename Source,
    typename = typename std::enable_if<sprout::is_string<Source>{}>::type
>
constexpr auto
operator <<(basic_stringstream<Size, String> const& ss, Source const& str)
->decltype(output(ss, str)){
    return output(ss, str);
}

template<std::size_t Size, typename String>
constexpr auto
operator <<(basic_stringstream<Size, String> const& ss, char c)
->decltype(output(ss, c)){
    return output(ss, c);
}

template<std::size_t Size, typename String, typename Source>
constexpr auto
operator <<(basic_stringstream<Size, String> const& ss, Source const& str)
->decltype(output(ss, sprout::to_string(str))){
    return output(ss, sprout::to_string(str));
}


//
//    brainfuck
//
template<typename Iter,
    typename Char = typename std::iterator_traits<Iter>::value_type
>
constexpr Iter
find_scope_end(Iter first, Iter last, std::size_t count = 0){
    return  first == last ? first
         : *first == Char('[') ? find_scope_end(sprout::next(first), last, count+1)
         : *first == Char(']') && count == 0 ? first
         : *first == Char(']') ? find_scope_end(sprout::next(first), last, count-1)
         : find_scope_end(sprout::next(first), last, count);
}


template<typename Iter,
    typename Char = typename std::iterator_traits<Iter>::value_type
>
constexpr Iter
find_scope_start(Iter first, Iter last, std::size_t count = 0){
    return  first == last ? last
         : *last == Char(']') ? find_scope_start(first, sprout::prev(last), count+1)
         : *last == Char('[') && count == 0 ? last
         : *last == Char('[') ? find_scope_start(first, sprout::prev(last), count-1)
         : find_scope_start(first, sprout::prev(last), count);
}


template<
    typename Iter,
    typename OStream,
    typename Buffer,
    typename InputIter,
    typename Char = typename std::iterator_traits<Iter>::value_type
>
constexpr OStream
brainfuck(
    Iter first, Iter mid, Iter last,
    OStream const& os,
    Buffer const& buffer,
    InputIter input,
    std::size_t ptr = 0
){
    namespace s = sprout;
    typedef s::reverse_iterator<Iter> reverse_iterator;
    return  mid == last ? os
         : *mid == Char('>') ? brainfuck(first, s::next(mid), last, os, buffer, input, ptr+1)
         : *mid == Char('<') ? brainfuck(first, s::next(mid), last, os, buffer, input, ptr-1)
         : *mid == Char('+') ? brainfuck(first, s::next(mid), last, os, s::set(buffer, ptr, Char(buffer[ptr]+1)), input, ptr)
         : *mid == Char('-') ? brainfuck(first, s::next(mid), last, os, s::set(buffer, ptr, Char(buffer[ptr]-1)), input, ptr)
         : *mid == Char('.') ? brainfuck(first, s::next(mid), last, os << buffer[ptr], buffer, input, ptr)
         : *mid == Char(',') ? brainfuck(first, s::next(mid), last, os, s::set(buffer, ptr, Char(*input)), s::next(input), ptr)
         : *mid == Char('[') && buffer[ptr] == 0 ? brainfuck(first, s::next(find_scope_end(s::next(mid), last)), last, os, buffer, input, ptr)
         : *mid == Char(']') && buffer[ptr] != 0 ? brainfuck(first, s::next(find_scope_start(first, s::prev(mid))), last, os, buffer, input, ptr)
         : brainfuck(first, s::next(mid), last, os, buffer, input, ptr);
}


int
main(){
    //
    // ファイルから Brainfuck のコードを読み込み
    //
    {
        static constexpr auto source = sprout::to_string(
            #include SOURCE_FILE
        );

        static constexpr auto buffer_size = 32;
    //     static constexpr auto output_size = sprout::range::count(source, '.');
        static constexpr auto output_size = 32;
        
        static constexpr auto buffer = sprout::fill(sprout::array<char, buffer_size>{}, char{0});
        static constexpr auto input = sprout::to_string("");
        static constexpr stringstream_lim<output_size> ss{};

        static constexpr auto result = brainfuck(
            sprout::begin(source), sprout::begin(source), sprout::end(source),
            ss,
            buffer,
            sprout::begin(input)
        );

        static_print<TO_STRING_TYPE(result.str())>();
    }
    
    
    //
    // 入力値を渡して加算
    //
    {
    static constexpr auto source = sprout::to_string(
        R"delimiter(
        ,>,>+<<
        >[-<+>]<
        ++++++++++++++++++++++++++++++++++++++++++++++++.
        )delimiter"
    );

        static constexpr auto buffer_size = 32;
        static constexpr auto output_size = 32;
        
        static constexpr auto buffer = sprout::fill(sprout::array<char, buffer_size>{}, char{0});
        static constexpr auto input = sprout::make_common_array(2, 5);
        static constexpr stringstream_lim<output_size> ss{};

        static constexpr auto result = brainfuck(
            sprout::begin(source), sprout::begin(source), sprout::end(source),
            ss,
            buffer,
            sprout::begin(input)
        );

        static_print<TO_STRING_TYPE(result.str())>();
    }

    return 0;
}

[brainfuck.txt]

R"delimiter(
>+++++++++[<++++++++>-]<.
>+++++++[<++++>-]<+.
+++++++.
.
+++.
[-]>++++++++[<++++>-]<.
>+++++++++++[<+++++>-]<.
>++++++++[<+++>-]<.
+++.
------.
--------.
[-]>
++++++++[<++++>-]<+.
[-]++++++++++.
)delimiter"

[出力]

In file included from brainfuck\main.cpp|38| 0:
|| D:/boost/boost_1_49_0/boost/mpl/print.hpp: In instantiation of 'struct boost::mpl::print<chars<'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!', '\012', '\000', '\000', '\000'> >':
main.cpp|44 col 42| required from 'void static_print() [with String = chars<'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!', '\012', '\000', '\000', '\000'>]'
main.cpp|203 col 46| required from here
\home\work\software\lib\cpp\boost\boost_1_49_0\boost\mpl\print.hpp|55 col 10| warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
||      enum {
||           ^
|| D:/boost/boost_1_49_0/boost/mpl/print.hpp: In instantiation of 'struct boost::mpl::print<chars<'7', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000'> >':
main.cpp|44 col 42| required from 'void static_print() [with String = chars<'7', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000'>]'
main.cpp|233 col 46| required from here
\boost\mpl\print.hpp|55 col 10| warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
||      enum {
||           ^

C++ のソースと同じディレクトリに brainfuck.txt を作っておくと、それが読みこまれて結果が出力されます。
まぁ #include "brainfuck.txt" とかしているだけなんですが。
残念ながらバッファのサイズは今のところ固定ですね…。


一応、評価時に値を渡すことで入力にも対応していたりします。
(後半の加算処理がそれ。


あと "Hello, World!" の出力とかは -fconstexpr-depth=4096 とかオプションを指定しておかないとコンパイルエラーになります☆(ゝω・)vキャピ
もうちょっと拡張性を高くして、派生言語にも対応してみたい。
Sprout.Weed 使ったバージョンも作ってみたいけど…どうなんだろうか。

[コンパイラ]

  • g++ (GCC) 4.8.0 20120415 (experimental)