1、 引言
最近用python写了一个模板引擎,类似Django模板的语法。其实最初写这东西的动机是想在C++用模板实现一些机械的代码。但是C++模板有些东西实现不了,或太难实现。比较C++模板不是这么容易可以掌握的。那难道这些机械的代码真的需要每次都手敲吗?或复制粘贴再修改?于是我自己给出了另一个方案,既然C++模板这么难,那我自己实现一个自己的模板,语法自己定,而且不限定只能C++用,所有语言都可以使用。项目地址CodeTemplate
不过需要提醒的是C++的模板还是很有必要深入学习的,有些东西用模板写出来有超乎想象的功能,包括泛型编程,和模板元编程。不过在还没有完全掌握之前用我这套方案还是可以的,而且模板引擎与目标语言无关,这个是本项目最大的优势。
其实模板引擎是一个很早的概念了,在WEB开发无处不在,像JAVA的struts2有自己的标签,JSP也有标签,Python 的Django有自己的模板语法。JS的框架就更多了,像谷歌的Angular,像Facebook的React,Vue我没有接触过,不过我打赌肯定有。
本项目,需要做词法分析,语法分析,但代码不超过300行。其中的思想我是参考Haskell的语法分析框架,以及王垠当年C++的Parser。当时看王垠的代码的确被震撼到了,原来代码可以这么写,从那时候才理解到函数式编程的威力。感觉突然进入了一个以前从未想象过的境界。以前觉得面向对象的思考模式实在是所向披靡,现在觉得代码无所谓OO。而且觉得很多情况OO的思考方式是不对的。以后找机会写一遍文章来介绍下王垠的解释器。
本项目本来想用lua来实现的,但是对lua不熟悉,算了Python吧,而且我看中Python的exec,eval这两个函数,有这两个函数的话实在方便很多。
2、 语法
变量
{{%VarName%}}
用{{%%}}括起来的代表变量,模板引擎会根据传进来的数据来替换这个变量
循环
{% for it in xlist %}
{{%it%}}
{% endfor %}
循环我只实现了for,不过我觉的应该足够了。需要{%for python语句%}{% endfor %}括起来。模板引擎会将循环输出括起来的内容。for循环里面可以嵌套for循环。
条件
{% if 条件 %}
内容
{% endif %}
模板引擎会根据条件是否满足来决定是否显示里面的内容。
3、以C++处理命令行参数为例
C++处理命令行参数很多使用getopt来处理,但是其实我很不喜欢这样的处理方式。C++ Main函数定义如下:
int main(int argc, char* argv[]){}
命令行参数是通过argc,argv传进来的。我希望把argc,argv转成一个结构体,这样比较方便处理。如一个命令行参数由
-x -cs -c -b
这四个参数构成,其中-x -cs 后面是需要带其他参数的,-c,-b后面是不带参数的,我还要按顺序记录下其他参数如命令行如下:
Text.exe -x XMLName -cs CSName -c -b TagFileName xx1 xx2 xx3 xx4
我希望把这些参数记录到一个如下的数据结构中
struct ARGS
{
string FileName;
string XMLFileName;
string CSFileName;
bool CFlag;
bool BFlag;
vector<string> Other;
};
其中把 XMLName存到XMLFileName中;CSName存到CSFileName中;有-c参数的话CFlag就为true,否则为false;-b同-c;TagFileName存在FileName,其他参数存在Other的vector中。我们需要一个函数来作为转换函数,我们暂且叫FromStringList把,函数类型如下:
ARGS FromStringList(int argc, char *argv[]);
我们用一个数据来描述我们的需求,用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指的是结构体的名字
参数名 | 意义 |
---|---|
IsPrefix | 0代表后面不带参数,1代表后面需要带参数 |
ArgName | 命令行参数名 |
Name | C++结构体属性名字 |
Type | C++结构体属性类型 |
使用项目的模板语言来实现如下
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))
输出内容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;
}
}
测试结果
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的高阶函数实现的话代码会简洁一些。如下
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))
当然它输出的C++代码是一样的。