1肘迎、 引言
最近用python寫了一個模板引擎安皱,類似Django模板的語法。其實最初寫這東西的動機(jī)是想在C++用模板實現(xiàn)一些機(jī)械的代碼房匆。但是C++模板有些東西實現(xiàn)不了,或太難實現(xiàn)。比較C++模板不是這么容易可以掌握的浴鸿。那難道這些機(jī)械的代碼真的需要每次都手敲嗎井氢?或復(fù)制粘貼再修改?于是我自己給出了另一個方案岳链,既然C++模板這么難花竞,那我自己實現(xiàn)一個自己的模板,語法自己定掸哑,而且不限定只能C++用约急,所有語言都可以使用。項目地址CodeTemplate
不過需要提醒的是C++的模板還是很有必要深入學(xué)習(xí)的苗分,有些東西用模板寫出來有超乎想象的功能厌蔽,包括泛型編程,和模板元編程摔癣。不過在還沒有完全掌握之前用我這套方案還是可以的奴饮,而且模板引擎與目標(biāo)語言無關(guān),這個是本項目最大的優(yōu)勢择浊。
其實模板引擎是一個很早的概念了戴卜,在WEB開發(fā)無處不在,像JAVA的struts2有自己的標(biāo)簽琢岩,JSP也有標(biāo)簽投剥,Python 的Django有自己的模板語法。JS的框架就更多了担孔,像谷歌的Angular江锨,像Facebook的React,Vue我沒有接觸過攒磨,不過我打賭肯定有泳桦。
本項目,需要做詞法分析娩缰,語法分析灸撰,但代碼不超過300行。其中的思想我是參考Haskell的語法分析框架拼坎,以及王垠當(dāng)年C++的Parser浮毯。當(dāng)時看王垠的代碼的確被震撼到了,原來代碼可以這么寫泰鸡,從那時候才理解到函數(shù)式編程的威力债蓝。感覺突然進(jìn)入了一個以前從未想象過的境界。以前覺得面向?qū)ο蟮乃伎寄J綄嵲谑撬蚺沂⒘洌F(xiàn)在覺得代碼無所謂OO饰迹。而且覺得很多情況OO的思考方式是不對的芳誓。以后找機(jī)會寫一遍文章來介紹下王垠的解釋器。
本項目本來想用lua來實現(xiàn)的啊鸭,但是對lua不熟悉锹淌,算了Python吧,而且我看中Python的exec赠制,eval這兩個函數(shù)赂摆,有這兩個函數(shù)的話實在方便很多。
2钟些、 語法
變量
{{%VarName%}}
用{{%%}}括起來的代表變量,模板引擎會根據(jù)傳進(jìn)來的數(shù)據(jù)來替換這個變量
循環(huán)
{% for it in xlist %}
{{%it%}}
{% endfor %}
循環(huán)我只實現(xiàn)了for,不過我覺的應(yīng)該足夠了烟号。需要{%for python語句%}{% endfor %}括起來。模板引擎會將循環(huán)輸出括起來的內(nèi)容政恍。for循環(huán)里面可以嵌套for循環(huán)汪拥。
條件
{% if 條件 %}
內(nèi)容
{% endif %}
模板引擎會根據(jù)條件是否滿足來決定是否顯示里面的內(nèi)容。
3抚垃、以C++處理命令行參數(shù)為例
C++處理命令行參數(shù)很多使用getopt來處理喷楣,但是其實我很不喜歡這樣的處理方式趟大。C++ Main函數(shù)定義如下:
int main(int argc, char* argv[]){}
命令行參數(shù)是通過argc,argv傳進(jìn)來的鹤树。我希望把a(bǔ)rgc,argv轉(zhuǎn)成一個結(jié)構(gòu)體逊朽,這樣比較方便處理罕伯。如一個命令行參數(shù)由
-x -cs -c -b
這四個參數(shù)構(gòu)成,其中-x -cs 后面是需要帶其他參數(shù)的叽讳,-c追他,-b后面是不帶參數(shù)的,我還要按順序記錄下其他參數(shù)如命令行如下:
Text.exe -x XMLName -cs CSName -c -b TagFileName xx1 xx2 xx3 xx4
我希望把這些參數(shù)記錄到一個如下的數(shù)據(jù)結(jié)構(gòu)中
struct ARGS
{
string FileName;
string XMLFileName;
string CSFileName;
bool CFlag;
bool BFlag;
vector<string> Other;
};
其中把 XMLName存到XMLFileName中岛蚤;CSName存到CSFileName中邑狸;有-c參數(shù)的話CFlag就為true呵哨,否則為false圾结;-b同-c;TagFileName存在FileName浇雹,其他參數(shù)存在Other的vector中她紫。我們需要一個函數(shù)來作為轉(zhuǎn)換函數(shù)硅堆,我們暫且叫FromStringList把,函數(shù)類型如下:
ARGS FromStringList(int argc, char *argv[]);
我們用一個數(shù)據(jù)來描述我們的需求,用python的字典來描述
codecontext = {
'Name':'ARGS',
'Rules':[
{'IsPrefix':0,'ArgName':'','Name':"FileName",'Type':'string'},
{'IsPrefix':1,'ArgName':'-x','Name':"XMLFileName",'Type':'string'},
{'IsPrefix':1,'ArgName':'-cs','Name':"CSFileName",'Type':'string'},
{'IsPrefix':0,'ArgName':'-c','Name':"CFlag",'Type':'bool'},
{'IsPrefix':0,'ArgName':'-b','Name':"BFlag",'Type':'bool'},
]
}
ARGS指的是結(jié)構(gòu)體的名字
參數(shù)名 | 意義 |
---|---|
IsPrefix | 0代表后面不帶參數(shù),1代表后面需要帶參數(shù) |
ArgName | 命令行參數(shù)名 |
Name | C++結(jié)構(gòu)體屬性名字 |
Type | C++結(jié)構(gòu)體屬性類型 |
使用項目的模板語言來實現(xiàn)如下
from CodeTemplate import Template
code = '''
struct {{%Name%}}
{
{% for it in Rules %}
{{%it['Type']%}} {{%it['Name']%}};
{% endfor %}
vector<string> Other;
static ARGS FromStringList(int argc, char *argv[])
{
ARGS ret ;
{% for it in Rules %}
{% if it['IsPrefix']==0 and it['Type'] == 'bool' %}
ret.{{%it['Name']%}} = false;
{% endif %}
{% endfor %}
int i=0;
while(++i<argc)
{
string current = argv[i];
{% for it in Rules %}
{% if it['IsPrefix']==1 and it['Type'] == 'string' %}
if(current.compare("{{%it['ArgName']%}}") == 0){
if(++i>=argc) throw exception("After {{%it['ArgName']%}} Is Nothing");
ret.{{%it['Name']%}} = argv[i];
continue;
}
{% endif %}
{% endfor %}
{% for it in Rules %}
{% if it['IsPrefix']==0 and it['Type'] == 'bool' %}
if(current.compare("{{%it['ArgName']%}}") == 0 ){
ret.{{%it['Name']%}} = true;
continue;
}
{% endif %}
{% endfor %}
{% for it in Rules %}
{% if it['ArgName']=="" %}
if(ret.{{%it['Name']%}}.empty())
{
ret.{{%it['Name']%}} = current;
continue;
}
{% endif %}
{% endfor %}
ret.Other.push_back(current);
}
return ret;
}
};
'''
#<RULE IsPrefix="0" ArgName="" Name="FileName" Type="string"/>
codecontext = {
'Name':'ARGS',
'Rules':[
{'IsPrefix':0,'ArgName':'','Name':"FileName",'Type':'string'},
{'IsPrefix':1,'ArgName':'-x','Name':"XMLFileName",'Type':'string'},
{'IsPrefix':1,'ArgName':'-cs','Name':"CSFileName",'Type':'string'},
{'IsPrefix':0,'ArgName':'-c','Name':"CFlag",'Type':'bool'},
{'IsPrefix':0,'ArgName':'-b','Name':"BFlag",'Type':'bool'},
]
}
print(Template(code)(codecontext))
輸出內(nèi)容C++代碼
struct ARGS
{
string FileName;
string XMLFileName;
string CSFileName;
bool CFlag;
bool BFlag;
vector<string> Other;
static ARGS FromStringList(int argc, char *argv[])
{
ARGS ret ;
ret.CFlag = false;
ret.BFlag = false;
int i=0;
while(++i<argc)
{
string current = argv[i];
if(current.compare("-x") == 0){
if(++i>=argc) throw exception("After -x Is Nothing");
ret.XMLFileName = argv[i];
continue;
}
if(current.compare("-cs") == 0){
if(++i>=argc) throw exception("After -cs Is Nothing");
ret.CSFileName = argv[i];
continue;
}
if(current.compare("-c") == 0 ){
ret.CFlag = true;
continue;
}
if(current.compare("-b") == 0 ){
ret.BFlag = true;
continue;
}
if(ret.FileName.empty())
{
ret.FileName = current;
continue;
}
ret.Other.push_back(current);
}
return ret;
}
};
我們把它拷入工程測試一下
#include <iostream>
#include <iostream>
#include <stdexcept>
#include <vector>
#include <string>
#include <sstream>
using namespace std;
struct ARGS
{
string FileName;
string XMLFileName;
string CSFileName;
bool CFlag;
bool BFlag;
vector<string> Other;
static ARGS FromStringList(int argc, char *argv[])
{
ARGS ret;
ret.CFlag = false;
ret.BFlag = false;
int i = 0;
while (++i<argc)
{
string current = argv[i];
if (current.compare("-x") == 0) {
if (++i >= argc) throw exception("After -x Is Nothing");
ret.XMLFileName = argv[i];
continue;
}
if (current.compare("-cs") == 0) {
if (++i >= argc) throw exception("After -cs Is Nothing");
ret.CSFileName = argv[i];
continue;
}
if (current.compare("-c") == 0) {
ret.CFlag = true;
continue;
}
if (current.compare("-b") == 0) {
ret.BFlag = true;
continue;
}
if (ret.FileName.empty())
{
ret.FileName = current;
continue;
}
ret.Other.push_back(current);
}
return ret;
}
};
int main(int argc, char* argv[])
{
try
{
auto _ARGS = ARGS::FromStringList(argc, argv);
cout << "CSFileName:" << _ARGS.CSFileName << endl;
cout << "XMLFileName:" << _ARGS.XMLFileName << endl;
cout << "FileName:" << _ARGS.FileName << endl;
cout << "BFlag:" << _ARGS.BFlag << endl;
cout << "CFlag:" << _ARGS.CFlag << endl;
for (auto it = _ARGS.Other.begin(); it != _ARGS.Other.end(); it++)
{
cout << "Other:" << *it << endl;
}
}
catch (exception& ex)
{
cout << ex.what() << endl;
}
}
測試結(jié)果
D:\VSWorkSpace\CTEST\Debug>CTEST.exe -cs csName -x xmlname -b -c tagfilename x1 x2 x3
CSFileName:csName
XMLFileName:xmlname
FileName:tagfilename
BFlag:1
CFlag:1
Other:x1
Other:x2
Other:x3
D:\VSWorkSpace\CTEST\Debug>CTEST.exe -cs
After -cs Is Nothing
D:\VSWorkSpace\CTEST\Debug>CTEST.exe -x
After -x Is Nothing
D:\VSWorkSpace\CTEST\Debug>CTEST.exe tag x1 x3 x3
CSFileName:
XMLFileName:
FileName:tag
BFlag:0
CFlag:0
Other:x1
Other:x3
Other:x3
其實我是為了測試for贿讹,if渐逃,變量才這么寫。如果用python的高階函數(shù)實現(xiàn)的話代碼會簡潔一些民褂。如下
from CodeTemplate import Template
code = '''
struct {{%Name%}}
{
{% for it in Rules %}
{{%it['Type']%}} {{%it['Name']%}};
{% endfor %}
vector<string> Other;
static ARGS FromStringList(int argc, char *argv[])
{
ARGS ret ;
{% for it in filter(lambda it:it['IsPrefix']==0 and it['Type'] == 'bool',Rules) %}
ret.{{%it['Name']%}} = false;
{% endfor %}
int i=0;
while(++i<argc)
{
string current = argv[i];
{% for it in filter(lambda it:it['IsPrefix']==1 and it['Type'] == 'string',Rules) %}
if(current.compare("{{%it['ArgName']%}}") == 0){
if(++i>=argc) throw exception("After {{%it['ArgName']%}} Is Nothing");
ret.{{%it['Name']%}} = argv[i];
continue;
}
{% endfor %}
{% for it in filter(lambda it: it['IsPrefix']==0 and it['Type'] == 'bool',Rules) %}
if(current.compare("{{%it['ArgName']%}}") == 0 ){
ret.{{%it['Name']%}} = true;
continue;
}
{% endfor %}
{% for it in filter(lambda it:it['ArgName']=="",Rules) %}
if(ret.{{%it['Name']%}}.empty())
{
ret.{{%it['Name']%}} = current;
continue;
}
{% endfor %}
ret.Other.push_back(current);
}
return ret;
}
};
'''
#<RULE IsPrefix="0" ArgName="" Name="FileName" Type="string"/>
codecontext = {
'Name':'ARGS',
'Rules':[
{'IsPrefix':0,'ArgName':'','Name':"FileName",'Type':'string'},
{'IsPrefix':1,'ArgName':'-x','Name':"XMLFileName",'Type':'string'},
{'IsPrefix':1,'ArgName':'-cs','Name':"CSFileName",'Type':'string'},
{'IsPrefix':0,'ArgName':'-c','Name':"CFlag",'Type':'bool'},
{'IsPrefix':0,'ArgName':'-b','Name':"BFlag",'Type':'bool'},
]
}
print(Template(code)(codecontext))
當(dāng)然它輸出的C++代碼是一樣的茄菊。