テンプレートタグ
CGIには向かないとされ見捨てられたC言語で、HTMLに近いお手軽記法と、
もとよりCなるが故の万能性と比類なき高速性とを鼎立させる、azaracの最大の特徴。
それは<%%>系列のタグに他ならない。
<%@ %> タグ
AzaraCが生成するHTMLに関するメタ情報を記述する場所です。
現在のところ
<%@ page Content-Type="text/html" %>
という構文でヘッダのContent-Typeを変更することが出来ます。
<%! %> タグ
ここに書かれたコードはテンプレートから生成されるC++のコードの
グローバルスコープに置かれます。
ヘッダのinclude、ロジック側の変数や関数の宣言、
またテンプレート内で使う関数を定義などを
記述するためのタグです。
<%= %> タグ
このタグの中に書いたコードは前から順に評価され、
最後の文の値が標準出力に出力されます。
<%= int a=3; a*a; %>
9
<% %> タグ
この中に書いたコードは、テンプレートから生成されるC++のコードにそのまま反映されます。
<% for (int i=0;i<3;++i) { %>
<%= i; %>
<% } %>
0
1
2
からくり
ここではAzaraCがどのような方法でテンプレートをロジックと切り離しているかということについて述べたいと思います。
重要なのは次の3点です
- テンプレートはC++のコードに変換され、共有ライブラリとしてコンパイルされる。
- ロジック側で定義された変数や関数をテンプレートから使うことが出来る。
- テンプレートが更新されていたら自動で再コンパイルされる。
これら全てをdisplay関数が解決しています。
まずテンプレートが実際にはどのようなC++のコードになるのかを見ることにしましょう。
例えば次のようなテンプレートを考えます。
<%@ page Content-Type="text/html; charset=UTF-8" %>
<%!
string hello() { return "hello"; }
%>
<html>
<body>
<% for (int i=0;i<10;++i) { %>
<%= hello() %>
<% } %>
</body>
</html>
<%@ %>タグ、<%! %>タグ、<% %>タグ、<%= %>タグ全てを用いている例を用意しました。
これはdisplay関数によって、次のように変換されます。
1 #include <azarac/azarac.h>
2 using namespace std;
3 using namespace azarac;
4 static ostringstream out;
5
6
7 string hello() { return "hello"; }
8
9 extern "C" void __scriptlet__(HTTPRequest& request, HTTPResponse& response) {
10 response.setHeader("Content-Type","text/html; charset=UTF-8" );
11 out<<"\n";
12 out<<"\n";
13 out<<"<html>\n";
14 out<<" <body>\n";
15 out<<" ";
16 for (int i=0;i<10;++i) {
17 out<<"\n";
18 out<<" ";
19 out<<({ hello(); });
20 out<<"\n";
21 out<<" ";
22 }
23 out<<"\n";
24 out<<" </body>\n";
25 out<<"</html>\n";
26 out<<"\n";
27 response.output();
28 cout<<out.str();
29 }
見やすいようにインデントは調整しました。
1行目はAzaraCのヘッダをincludeしているところです。
これによって全てのテンプレートはAzaraC内の関数やクラスを用いることが出来るようになっています。
これらはnamespace azarac以下にあるのですが、テンプレート内では利便性の為3行目にあるように
using namespace azarac;されるようになっています。
7行目には<%! %>タグの中に書いたhello関数の定義が記述されています。
9行目から定義されている__scriptlet__関数がテンプレートの核となる処理を担うところです。
テンプレートをコンパイルした後、ロジック側がこの__scriptlet__関数を呼び出すことで、HTMLを標準出力に出力しています。
引数のHTTPRequest& requestはクライアント側から送られてきた情報が入っており、
またHTTPResponse& responseはクライアントに返す情報を格納するオブジェクトとなっています。
10行目には<%@ %>タグの中で指定したヘッダ情報が使われています。
これをresponseオブジェクトのsetHeader関数の引数に指定することで、クライアントに送る返答のヘッダの設定しています。
11行目から15行目はテンプレートの地の部分をoutオブジェクトに流し込んでいます。
16行目に<% %>タグ内にあるfor文が書かれています。
これはoutオブジェクトに流し込まれることはなく、<% %>の中がそのままC++のコードとして使われています。
19行目は<%= %>タグです。
({ })という構文はgccの拡張で({a; b; c;})とするとブロック内のコードが普通に、つまりa->b->cの順に評価され、
最後の文であるcの値がこのブロック全体の結果となります。
ここではhello関数を呼び出しているだけなので、結果は"hello"となります。
そしてその結果をoutオブジェクトに流し込んでいます。
このように最後の結果をoutオブジェクトに送るかどうかが<% %>タグと決定的に異なるところです。
それ以降26行目までは同じような処理が続きます。
最後に27行目のresponse.output()でヘッダを出力し、
28行目のcout<<out.str();でHTML本文を出力しています。
これがテンプレートをC++に変換したコードの全体の流れです。
テンプレートをC++に変換した後は、これを共有ライブラリとしてコンパイルし、
コンパイルに成功すればそれをdlopenして__scriptlet__関数を呼び出しています。
この仕組みにすることで、テンプレートの変更に強いフレームワークになっているというわけです。
自分が生成した共有ライブラリを自分が読み込むというのはなかなかしゃれているとは思いませんか?
ロジック側で定義された変数や関数をテンプレート側で使うという機能もdlopenを用いて実現されています。
普通の外部シンボルはそのライブラリに記述された依存関係を用いて解決するのですが、
dlopenした側のプログラムが-rdynamicオプションつきでコンパイルされていると、
このプログラム中にあるシンボルもライブラリの外部シンボルの解決に用いられるようになります。
こうすることでテンプレートとロジックを切り分けつつ、
テンプレートからはロジックのデータが見えるという
絶妙な関係が築けるのです
テンプレートが更新されていたら再コンパイルされるという仕組みはとても単純で、
テンプレートの更新時刻とそれから生成された共有ライブラリの更新時刻を比較しているだけです。
テンプレートの方が共有ライブラリよりも新しければ、その場で再コンパイルします
このような流れでdisplay関数はテンプレートをC++に変換し、コンパイルし、ロードします。
これらの作業によってAzaraCの柔軟性が生まれているのです。
|