CowboyとErlydtlで逆ポーランド式を計算するアプリケーションを作成

今回やりたいことは下の図で示します．
![Screen Shot 2015-02-28 at 12.38.00 AM.png](https://qiita-image-store.s3.amazonaws.com/0/54698/1bef9066-3a34-a8b2-56ab-58c44821be2d.png)

![Screen Shot 2015-02-28 at 12.39.21 AM.png](https://qiita-image-store.s3.amazonaws.com/0/54698/d61b404c-b3d2-5109-af7a-67fa4a89aae5.png)

つまり逆ポーランド記法をウェブ上で入力して，サーバー側で計算してから，答えを返すという実用性ゼロのプログラムを作りたいと思います．しかも中途半端な画面で申し訳ないです．要はErlangのCowboyでアプリケーションサーバーを作る練習をしたいです．これを作る途中に私にとっては一番ややこしいのはErlangのBinary, List, Integerの間の変換でした．

前提知識としてまず，

- Erlangで逆ポーランド記法の書き方について下のリンクで解説されています．

    http://www.ymotongpoo.com/works/lyse-ja/ja/10_functionally_solving_problems.html

- そして前回の投稿を目を通していただきたいです．今回はこのソースをベースに作られているものです．

    http://qiita.com/ColdFreak/items/8aaa71d0bc4270e0970c

今回はEchoサーバーを逆ポーランド記法を計算してくれるサーバーに変更させます．

大いに変更したのは`src/toppage_handler.erl`ファイルです．

```erlang
$ cat toppage_handler.erl                                                                                                
%% Feel free to use, reuse and abuse the code in this file.
-module(toppage_handler).
-export([init/2]).

init(Req, Opts) ->
    Method = cowboy_req:method(Req),
    HasBody = cowboy_req:has_body(Req),
    Req2 = maybe_echo(Method, HasBody, Req),
    {ok, Req2, Opts}.

maybe_echo(<<"POST">>, true, Req) ->
    {ok, PostVals, Req2} = cowboy_req:body_qs(Req),
    Echo = proplists:get_value(<<"echo">>, PostVals),
    Result = do_rpn(Echo),
    echo(Result, Req2);
maybe_echo(<<"POST">>, false, Req) ->
    cowboy_req:reply(400, [], <<"Missing body.">>, Req);
maybe_echo(_, _, Req) ->
    %% Method not allowed.
    cowboy_req:reply(405, Req).

echo(undefined, Req) ->
    cowboy_req:reply(400, [], <<"Missing echo parameter.">>, Req);
echo(Echo, Req) ->
    cowboy_req:reply(200, [
        {<<"content-type">>, <<"text/plain; charset=utf-8">>}
    ], Echo, Req).

do_rpn(B) when is_binary(B) ->
    L = binary_to_list(B),
    [Res] = lists:foldl(fun do_rpn/2, [], string:tokens(L, " ")),
    case is_float(Res) of
        true ->
            list_to_binary(float_to_list(Res, [{decimals, 2}]));
        false ->
            list_to_binary(integer_to_list(Res))
    end.

read(N) ->
    case string:to_float(N) of
        {error, no_float} ->
            list_to_integer(N);
        {F, _} ->
            F
    end.

do_rpn("+", [N1, N2|S]) -> [N2 + N1|S];
do_rpn("-", [N1, N2|S]) -> [N2 - N1|S];
do_rpn("*", [N1, N2|S]) -> [N2 * N1|S];
do_rpn("/", [N1, N2|S]) -> [N2 / N1|S];
do_rpn("^", [N1, N2|S]) -> [math:pow(N2, N1)|S];
do_rpn("ln", [N|S])     -> [math:log(N)|S];
do_rpn("log10", [N|S])  -> [math:log10(N)|S];
% sumの場合も同じで、戻り値はfoldlの二個目の引数を書き換える
% 要はアキュミュレータは足されていく
do_rpn("sum", Stack)    -> [lists:sum(Stack)];
do_rpn(X, Stack)        -> [read(X) | Stack].
```

do_rpn/1関数を使って，逆ポーランド式を計算して，結果を返します．

**ここで気をつけるところはCowboyがユーザー入力の内容をbinary stringとして，取得して
`Echo = proplists:get_value(<<"echo">>, PostVals),`**

**do_rpn/1関数が一旦そのbinaryをリストにして，計算するようにしています.
`L = binary_to_list(B),`**

**そして計算した結果をもう一回binaryに戻して，クライアントに送ります．
`list_to_binary(float_to_list(Res, [{decimals, 2}]));`**

index.htmlファイルも少し修正しましたが，大したこと書いてません．

```html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
<!-- <link rel="stylesheet" href="styles.css"> -->
<style>
div {
    width:300px;
    height:300px;
    border-style: solid;
    border-width: 1px;
    margin: 100px auto;
    box-shadow: 10px 10px 5px #888888;
}
form {
    text-align: center;
    margin: 100px auto;
}
</style>
</head>
<body>
<div >
    <form method="post" action="/process" class="form-horizontal" role="form">
        <input type="text"  name="echo">
        <input type="submit" value="submit">
    </form>
</div>
</body>
</html>
```

これでコンパイルしてからアプリケーションを立ち上げます．index.htmlを修正するたびにコンパイル必要みたいです．

```
$ make
$ _rel/cowboy_post_release/bin/cowboy_post_release console
```

----

上のコードをベースにしてテンプレートエンジンerlydtlを使いたいと思います。

最終の画面のは下のようになります。

![スクリーンショット 2015-03-02 15.14.46.png](https://qiita-image-store.s3.amazonaws.com/0/54698/f9204064-763a-3b07-ecd9-c5e4e5628e7e.png)

![スクリーンショット 2015-03-02 15.15.04.png](https://qiita-image-store.s3.amazonaws.com/0/54698/c0164bd7-0998-6273-b82b-cbf90f205df0.png)


背景知識としてまずはerlydtlを読んでもらいたいです。

https://github.com/erlydtl/erlydtl/wiki

最初に修正するファイルはcowboy_post/Makefileで

```mf
PROJECT = cowboy_post
DEPS = cowboy erlydtl merl
dep_cowboy = git https://github.com/ninenines/cowboy master
dep_erlydtl = git https://github.com/erlydtl/erlydtl master
dep_merl = git https://github.com/richcarl/merl master
include erlang.mk
```

そして cowboy_post/src/cowboy_post.app.srcファイル

```
{application, cowboy_post, [
	{description, ""},
	{vsn, "0.1.0"},
	{id, "git"},
	{modules, []},
	{registered, []},
	{applications, [
		kernel,
		stdlib,
        cowboy,
        erlydtl,
        merl
	]},
	{mod, {cowboy_post_app, []}},
	{env, []}
]}.
```
念のため,merlを追加しましたけど、多分なくても動くはずです。

そのあと、cowboy_postディレクトリ下にtemplatesディレクトリを作成、そのディレクトリの中にecho.dtlファイルの作成します。

```html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
</head>
<style>
div {
    width:300px;
    height:300px;
    border-style: solid;
    border-width: 1px;
    margin: 100px auto;
    box-shadow: 10px 10px 5px #888888;
}
form {
    text-align: center;
    margin-top: 100px;
    margin-left: 25px;
}

p {
    margin-left : 45px;
}
</style>
<body>
    <div>
    <form method="post" action="/process" class="form-horizontal" role="form">
        <input type="text"  name="echo">
        <input type="submit" value="mysubmit">
    </form>
    <p> {{ answer }} </p>
    </div>
</body>
</html>
```

それに合わせて、cowboy_post/priv/static/index.htmlファイルも修正します。

```html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
</head>
<style>
div {
    width:300px;
    height:300px;
    border-style: solid;
    border-width: 1px;
    margin: 100px auto;
    box-shadow: 10px 10px 5px #888888;
}
form {
    text-align: center;
    margin-top: 100px;
    margin-left: 25px;
}

p {
    margin-left : 45px;
}
</style>
<body>
    <div>
    <form method="post" action="/process" class="form-horizontal" role="form">
        <input type="text"  name="echo">
        <input type="submit" value="mysubmit">
    </form>
    </div>
</body>
</html>
```

この段階で `$ make app`コマンドを打ったらcowboy_post/ebin/echo_dtl.beamファイルが現れます。今打たなくても大丈夫で、修正箇所はまだ残っています。

cowboy_post/src/toppage_handler.erlファイルを下のように修正します。

```erlang
%% Feel free to use, reuse and abuse the code in this file.

-module(toppage_handler).

-export([init/2, do_rpn/1]).

init(Req, Opts) ->
    Method = cowboy_req:method(Req),
    HasBody = cowboy_req:has_body(Req),
    Req2 = maybe_echo(Method, HasBody, Req),
    {ok, Req2, Opts}.

maybe_echo(<<"POST">>, true, Req) ->
    {ok, PostVals, Req2} = cowboy_req:body_qs(Req),
    Echo = proplists:get_value(<<"echo">>, PostVals),
    Result = do_rpn(Echo),
    {ok, L} = echo_dtl:render([
                {answer, Result}]),
    Result2 =  list_to_binary(L),
    echo(Result2, Req2);
maybe_echo(<<"POST">>, false, Req) ->
    cowboy_req:reply(400, [], <<"Missing body.">>, Req);
maybe_echo(_, _, Req) ->
    %% Method not allowed.
    cowboy_req:reply(405, Req).

echo(undefined, Req) ->
    cowboy_req:reply(400, [], <<"Missing echo parameter.">>, Req);
echo(Echo, Req) ->
    cowboy_req:reply(200, [
        {<<"content-type">>, <<"text/html; charset=utf-8">>}
    ], Echo, Req).

do_rpn(B) when is_binary(B) ->
    L = binary_to_list(B),
    [Res] = lists:foldl(fun do_rpn/2, [], string:tokens(L, " ")),
    case is_float(Res) of
        true ->
            list_to_binary(float_to_list(Res, [{decimals, 2}]));
        false ->
            list_to_binary(integer_to_list(Res))
    end.

read(N) ->
    case string:to_float(N) of
        {error, no_float} ->
            list_to_integer(N);
        {F, _} ->
            F
    end.

do_rpn("+", [N1, N2|S]) -> [N2 + N1|S];
do_rpn("-", [N1, N2|S]) -> [N2 - N1|S];
do_rpn("*", [N1, N2|S]) -> [N2 * N1|S];
do_rpn("/", [N1, N2|S]) -> [N2 / N1|S];
do_rpn("^", [N1, N2|S]) -> [math:pow(N2, N1)|S];
do_rpn("ln", [N|S])     -> [math:log(N)|S];
do_rpn("log10", [N|S])  -> [math:log10(N)|S];
% sumの場合も同じで、戻り値はfoldlの二個目の引数を書き換える
% 要はアキュミュレータは足されていく
do_rpn("sum", Stack)    -> [lists:sum(Stack)];
do_rpn(X, Stack)        -> [read(X) | Stack].
```

要は下のコードを追加されました。

```
   {ok, L} = echo_dtl:render([
                {answer, Result}]),
```

後レスポンスはplainではなくて、htmlにします。

```erlang
cowboy_req:reply(200, [
        {<<"content-type">>, <<"text/html; charset=utf-8">>}
    ], Echo, Req).
```

これでcowboy_postディレクトリに下のコマンドを打つと、出来上がります。

```
$ make app
$ make
$ _rel/cowboy_post_release/bin/cowboy_post_release console
```