代码在此:http://download.csdn.net/detail/u012419410/8618317
2013-10-30 14:12
457人阅读
收藏
举报
引言
make_cpp_class.py这个项目的目的是使产生c++类的头文件和实现文件更容易。这个项目经常可以制造出正确的代码,可是,这个项目常常不能确保包含之前的头文件,也因此生成的代码在被成功编译之前可能必须要编辑。这个项目保存了很多打印文件。
这个项目是为那些有经验的c++程序员准备的,因为要理解这些为生成代码而造成的改变,就需要他们理解c++语言。
|
这个项目将文件作为输入、类名和一些选择开关参数。这个输入文件由一种简单的语言编写的代码构成。一旦使用这个项目生成了代码,输入文件就会被丢弃。
输入文件中的任何方法体都是未做修改的被复制用来生成代码。换句话说,你做任何事情仍然要编写代码,这个项目仅仅消除了写c++类公式化代码的需要。
当通过命令行时,生成了和类名有着相同名字的文件夹,而且生成的代码也包含在这个文件夹中。
|
程序文件
- make_cpp_class.py - 主程序文件,用来解析参数并且调用代码生成器.
- bclass.py -代码生成器的核心模块,用来解析输入的文件同时创建生成的文件.
- bparse.py - 解析输入的文件.
- file_buffer.py - 缓存输入文件的数据; 被bparse.py所使用
- function_info.py - 存储方法的信息,包括方法名、返回值类型、参数类型等.
- include_file_manager.py - 存储一些标准类型的文件名.
- type_and_name_info.py - 存储一个名字,可能是变量或者方法的名字,另外还有一个类型信息实例.
- type_info.py - 存储某个类型的属性.
- test_input.txt - 一个可以被用来作为程序的输入从而展示程序能力的文件. 这个文件不属于程序的一部分.
|
使用代码 程序使用一个输入文件名,一个类名,还有一些可选的开关参数作为输入. 输入文件中包含使用很简单的语言写成的代码. 一旦使用该程序生成了代码,输入文件就可以被丢弃.
输入文件中的任何方法体都被原封不动的复制到生成的代码中. 换句话说,你仍然需要写那些不做任何事情的代码,程序仅仅是消除样板文件,而当生成C++类的时候,这是必须写的.
输入语言就想下面描述的一样,看看'test_input.txt'文件,你将会对这写描述信息更加清楚.
|
程序对输入文件中的数据会做很少的词法分析. 尽管有些序列会被认为是错误的,如果输入文件不正确,程序就可能产生垃圾.这就是“如果你使用这个程序,了解C++很重要”的另一个原因.
使用方式:
python make_cpp_class.py <class_name> <input_file_name> [-b base_class_name] [-a author_name] [-f] [-t]
程序可以接受下面的参数:
-b base_class_name, --base_class base_class_name - 继承自指定的父类.
-a author_name, --author author_name - 作者名字.
-f, --full - 写详细全面的头部信息.
-t, --abstract - 是所有的虚拟方法成为抽象方法.
-h, --help - 显示帮助并且退出
|
输入文件格式
输入文件中的数据成员格式
数据成员是文件中的第一部分,他们被按照下面的格式定义: <类型> <名字> [= 初始值]<;> 类型可以是指针参数,引用参数,前面可以有const,volatile或者static修饰. 'Const volatile", 尽管有效,但不被允许放在一起. 我没有这样实现仅仅为了简化解析器;如果你有这样的需求,你可以使用其中的一个关键字,然后将另一个关键字加到生成的代码中. 一个数据成员集合样例可以是这样的:
- short m_age;
- int m_count = 7;
- Foo_t * m_foo;
- static const float m_height = 1.0;
静态数据成员生成头文件和实现文件中的合适代码.如果某个数据成员被指定了初始值,那么在等号和结束的分号之间的所有东西在生成的代码中都将被作为初始值使用.
|
构造器和销毁器
定义完数据成员之后,构造器,销毁器和方法紧随其后.所有的方法的头部都是自动生成的. 方法的返回值可以被定义成与数据成员相同的类型,并且允许被关键字'inline'和'virtual'修饰. 它们不能被同时使用,并且都应该位于方法定义所在行的最前端. 由于类名是在命令行指定的,当被构造器和销毁器使用的时候,类名在输入文件中使用'@'指定.
下面的例子展示了两个构造器和一个虚拟的销毁器.
- @()
- {
- }
-
- @(int x)
- {
-
- }
-
- virtual ~@()
- {
- }
|
每个构造器都将会被添加成员初始化列表. 成员初始化列表可能需要进行编辑,但大多数时候会使用生成的代码.
如果命令行中传递的类名是 'Foobar' 并且数据成员列表就是上面的例子中展示的, 然后是第一个构造器的定义,它以'@'字符开始,那么将会产生下面的代码:
- Foobar::Foobar()
- : m_age(0),
- , m_count(7)
- , m_foo(NULL)
- {
- }
|
复制构造器和等号操作符
将关键字"copy:" 放到输入行上,将会自动生成复制构造器和等号操作符的代码. 这两个方法都将调用生成的'Copy'方法,该方法将会对类的数据成员进行一次浅拷贝.
将关键字 "nocopy:" 放到输入行上,将会生成一个'private'的复制构造器和等号操作符, 它们都没有任何实现,该类的实例将不能被拷贝.
同时使用"copy:" 和 "nocopy:" 将会生成错误信息.
|
方法
方法的定义与C语言的方法类似, 当方法可以通过在末尾添加'const'关键字而成为不可变的. 另外, "= 0" 可以被添加到方法的末尾以使方法成为抽象方法. 如果使用了"= 0" ,那么在类的实现文件中就不会生成方法体. zh:
- double accumulate(double addend)
- {
- m_sum += addend;
- return m_sum;
- }
-
- const & Foobar GetFoobar() const
- {
- return m_foobar;
- }
-
- virtual int doSomething(int anIntegerToUseForSomething) const
- {
-
-
-
- }
方法也可以使用'static'关键字声明.
|
属性方法
有另外一个特别的关键字用来定义属性. 属性的数据成员不应该被定义在上面提到的数据成员部分. 生成属性的语法需要使用":property"关键字,如下: :property <类型> <数据成员名称> <属性名称> 这个定义将会创建一个名如set<属性名>和一个名如get<属性名>的方法. 这里有一个属性定义的例子.
上面的属性定义将导致代码生成器生成下面的数据成员和方法. 为了简洁期间,代码头部被省略了. 另外,数据成员m_age应该被定义在类头部文件中的数据成员部分.
- int m_age;
-
- int Age() const
- {
- return m_age;
- }
-
- void setAge(int the_value)
- {
- m_age = the_value;
- }
如果定义属性时使用的数据类型不是内在类型,那么代码生成器生成的代码将与上面的略有不同,例如:
- property: Person_t * m_person Person
-
- Person_t * m_person;
-
- Person_t Person() const
- {
- return m_person;
- }
-
- void setPerson(const Person_t * the_value)
- {
- m_person = the_value;
- }
|
消息主体
解析器通过解析方法签名,直到第一个开括号字符或者'{',来检测一个方法体的开始. 从那一刻开始,忽略字符串声明中的括号,开括号让一个初始为0的计数器递增,而闭括号'}'则让该计数器递减,当计数器重新回到0的时候,方法体就算结束了.
如果输入文件中的括号不正确,程序将会产生错误代码.
|
这个项目的缺点
上面已经提到过,输入文件存在一个最小的词法分析,那么输入了无用的数据就会产生无用的数据。
当然,这个项目做了这样一个假设:所有类名都已被指明或者没有被内置(没有被建立)或者不是特殊类型名——那些被文件生成器代码使用,将会产生一个包含有头文件——和类名名称相同且以.h为后缀的包含声明的文件。当然,这个头文件名可能经常无效,因此这将经常需要删除这个包含声明或者改变这个包含声明里的头文件的名称。
当然,不是代码主体里的任何数据类型都被发现,这就可能需要在生成的代码里包含头文件来使用这些数据。
|
建议与诀窍
如果我知道程序将不能自动生成某些数据,比如注释块,或者“include语句“,而且我需要这些数据出现在类的头文件里,那么我将如下这样声明一个内联函数:
- inline void dummy()
- {
- #include "foobar.h"
- #include "barfoo.h"
- }
然后,我编辑这个文件, 把这部分文本移动到它要出现的地方,并删除不需要的方法。
另外,使用@作为类名的情况下,可以通过多次运行这个程序并在输入行传递不同的类名和相同的基类的方法自动生成具有继承同一个基类的方法名的多个类。
|
兴趣点
编写这个程序的出发点是删除C++头文件和C++实现文件里的所有多余信息所需的代码,并编写出每个类都必须的所有模板代码。
这样的程序可以编写的更好,不过编写一个可以毫无错误地处所有情况的程序花费的时间如果不是几年的话就需要许多月。对我来收,这比仅仅在这个工具里编写,然后修正包含文件的小错误要慢一些。
然而,如果有数千人将使用它自动生成代码的话,创建一种输入语言语法就可能是值得的,可以使用yacc,或者ANTLR创建解析器,然后再增加代码自动生成器。这个设想的程序还可以解析消息体,并变得更加强大。
|