包装类型
Wraptype是一个通用实用程序,用于从C头文件创建ctype包装器。前端是 tools/wraptypes/wrap.py ,用于用法:
python tools/wraptypes/wrap.py -h
包装类型有三个组件:
- preprocessor.py
解释预处理器声明并将源头文件转换为令牌列表。
- cparser.py
分析用于类型和函数声明和调用的预处理标记
handle_类CParser上的方法,其方式与SAX解析器类似。- ctypesparser.py
解释CParser中的C声明和类型,并创建相应的ctype声明,调用
handle_类CtyesParser上的方法。
前端 wrap.py 提供一个简单的子类 CtypesParser , CtypesWrapper ,它将找到的ctype声明以可以作为模块导入的格式写入文件。
解析器修改
解析器基于修改后的版本构建 PLY ,lex和yacc的一个Python实现。修改后的源代码包含在 wraptypes 目录。有关的修改如下:
语法是从Parser中抽象出来的,因此可以很容易地在同一模块中定义多种语法。
标记和符号跟踪它们的文件名和行号。
词法分析器状态可以推送到堆栈上。
第一次运行解析器(或在修改解析器之后)时,PLY将创建 pptab.py 和 parsetab.py 在当前目录中。这些是生成的状态机,可能需要几秒钟才能生成。档案 parser.out 如果启用了调试,则创建,并包含(生成的最后一个解析器的)解析器描述,这对于调试是必不可少的。
预处理器
语法和解析器在中定义 preprocessor.py 。
只有一个词法分析器状态。每个令牌都有一个字符串类型(例如 'CHARACTER_CONSTANT' )和值。直接从源文件读取时,令牌值仅为永远字符串。将令牌写入输出列表时,它们有时具有元组值(例如, PP_DEFINE 输出上的令牌)。
定义了两个词法分析器类: PreprocessorLexer ,它读取一堆文件(实际上是字符串)作为输入,以及 TokenListLexer ,它从已解析的令牌列表中读取(用于解析表达式)。
预处理入口点是 PreprocessorParser 班级。这将创建一个 PreprocessorLexer 以及它在构造过程中的语法。默认情况下,系统包含路径包括GCC搜索路径,但可以通过更改 include_path 和 framework_path 列表。这个 system_headers DICT允许在搜索路径上隐含不存在的头文件。例如,通过设置:
system_headers['stdlib.h'] = '''#ifndef STDLIB_H
#define STDLIB_H
/* ... */
#endif
'''
您可以插入自己的定制标头来代替文件系统上的标头。这在解析来自网络位置的标头时非常有用。
解析在以下时间开始 parse 被称为。指定文件名和数据字符串中的一个或两个。如果 debug Kwarg为True,则语法错误将转储解析器状态,而不仅仅是它们出现的行号。
产生式规则指定操作;这些操作在 PreprocessorGrammar 。这些操作调用 PreprocessorParser ,例如:
include(self, header),将另一个文件推送到词法分析器。include_system(self, header),在系统路径中搜索要推送到词法分析器上的文件error(self, message, filename, line),发出解析错误的信号。由于解析器中的限制,并不是所有的语法错误都会出现这种情况。EOF处的解析错误将仅打印到stderr。write(self, tokens),将令牌写入输出列表。当没有分析任何预处理声明时,这是默认操作。
解析器有一个堆栈 ExecutionState ,它指定是否忽略正在分析的当前标记(在 #if 它的计算结果为0)。这比布尔标志稍微复杂一些:解析器还必须忽略不起作用的#elif条件。这个 enable_declaratives 和 enable_elif_conditionals 如果最顶层的 ExecutionState 允许声明和 #elif 要分别解析的条件句。执行状态堆栈使用 condition_* 方法:研究方法。
PreprocessorParser 有一个 PreprocessorNamespace 它跟踪当前定义的宏。您可以创建和指定自己的命名空间,也可以使用默认情况下创建的命名空间。默认命名空间包括解析系统头所需的GCC平台宏,以及一些STDC宏。
将令牌写入输出列表以及分析条件表达式时,将展开宏。 PreprocessorNamespace.apply_macros(tokens) 解决了这一问题,替换了函数参数、变量参数、宏对象,并(主要)避免了无限递归。它还不能处理 # 和 ## 运算符,它们是解析Windows系统标头所需的。
计算条件的过程 (#if 或 #elif )是:
之间的令牌
PP_IF或PP_ELIF和NEWLINE被扩展为apply_macros。生成的令牌列表用于构造
TokenListLexer。此词法分析器用作
ConstantExpressionParser。此解析器使用ConstantExpressionGrammar,这构建了一个ExpressionNode物体。parse对象上调用ConstantExpressionParser,它返回结果的顶层ExpressionNode,或None如果存在语法错误。这个
evaluate的方法。ExpressionNode使用预处理器的命名空间作为求值上下文进行调用。这允许表达式节点解析defined操作员。其结果是
evaluate始终为int;非零值被视为True。
因为pyglet需要源代码中遇到的预处理器声明的特殊知识,所以这些声明被编码为输出令牌列表中的伪令牌。例如,在一个 #ifndef 被计算时,它将作为 PP_IFNDEF 代币。
#define 是特别处理的。将其应用于命名空间后,它立即被解析为表达式。这是被允许的(通常也是意料之中的)失败。如果它没有失败,则一个 PP_DEFINE_CONSTANT 创建令牌,该值是对表达式求值的结果。否则,一个 PP_DEFINE 创建令牌,该值是定义的令牌的字符串串联。对可解析表达式的特殊处理使以后可以简单地解析定义为::
#define RED_SHIFT 8
#define RED_MASK (0x0f << RED_SHIFT)
可以通过运行以下命令来测试/调试预处理器 preprocessor.py 使用头文件作为唯一参数的独立。生成的令牌列表将写入标准输出。
CParser
的词法分析器 CParser , CLexer 将预处理器输出的令牌列表作为输入。特殊的预处理器令牌,如 PP_DEFINE 在这里被拦截并立即处理;因此,它们可以出现在源头文件中的任何位置,而不会导致解析器出现问题。在这一点上 IDENTIFIER 被发现是已定义类型(已定义类型集在分析过程中不断更新)名称的标记被转换为 TYPE_NAME 代币。
解析C源代码的入口点是 CParser 班级。这将在其构造函数中创建一个预处理器,并定义一些默认类型,如 wchar_t 和 __int64_t 。可以使用Kwargs禁用这些功能。
预处理可能相当耗时,尤其是在OSX上 #include 在解析Carbon时会处理声明性语句。为了最大限度地减少解析相似(或相同,在调试时)头文件所需的时间,会缓存来自预处理的令牌列表并在可能的情况下重复使用。
这由以下人员处理 CPreprocessorParser ,它会重写 push_file 与…核对 CParser 如果缓存了所需的文件。根据文件的修改时间戳以及描述当前定义的令牌的“纪念品”来检查缓存。这是为了避免使用缓存的文件,否则会因为定义的宏而对其进行不同的解析。它绝不是完美的;例如,它不会接受定义不同的宏。对于pyglet所需的头文件,它似乎工作得足够好了。
标头缓存在工作目录中保存并自动加载为 .header.cache 。如果您对预处理器进行了更改,或者遇到了缓存错误(这些错误通常伴随着“What-the?”来自用户的感叹)。
语法中的操作构成了“C对象模型”的一部分,并调用 CParser 。C对象模型并不完整,它只包含了pyglet(以及任何其他包装ctype的应用程序)所需的内容。对象模型中的类包括:
- 申报
出现在函数体之外的单个声明。这包括类型声明、函数声明和变量声明。这些属性包括
declarator(见下文),type(类型对象)和storage(例如,‘tyecif’、‘const’、‘Static’、‘extern’等)。- 声明符
声明者是一种被声明的东西。破坏者有一个
identifier(它的名称,如果声明符是抽象的,则为None,就像在某些函数参数声明中一样),可选的initializer(当前被忽略),一个可选的链接列表array(给出数组的维度)和可选的parameters(如果声明符是一个函数)。- 指针
这是一种声明符类型,通过
pointer传递给另一个声明者。- 数组
数组具有大小(整型、其维度,如果未调整大小则为无)和一个指针
array设置为下一个数组维度(如果有的话)。- 参数
函数参数,由
type(类型对象),storage和declarator。- 类型
类型有一个列表,
qualifiers(例如,‘const’、‘Volatile’等)和specifiers(肉质的一部分)。- TypeSpecifier
基本类型说明符只是一个字符串,例如
'int'或'Foo'或'unsigned'。请注意,类型可以有多个类型说明符;并非所有组合都有效。- StructTypeSpecifier
这是结构或联合的说明符(如果
is_union为True)类型。tag提供可选的foo在……里面struct foo和declarations是肉(不透明或未指定结构的空列表)。- EnumSpecifier
这是枚举类型的说明符。
tag提供可选的foo在……里面enum foo和enumerators是枚举器对象的列表(未指定枚举的空列表)。- 枚举器
枚举数仅存在于EnumSpecifier中。包含
name和expression,ExpressionNode对象。
这个 ExpressionNode 对象层次结构类似于预处理器中使用的层次结构,但功能更全,并使用不同的 EvaluationContext 它可以计算标识符和 sizeof 运算符(目前它实际上只为两个都返回0)。
在解析声明和预处理器声明时,在CParser上调用方法。它们大多是不言而喻的。例如:
- Handle_ifndef(self,name,filename,lineno)
一个
#ifndef在测试宏时遇到name在文件中filename在线路上lineno。- HANDLE_DECLARATION(自身,声明,文件名,行号)
declaration是《宣言》的一个例子。
这些方法应该由子类覆盖以提供功能。这个 DebugCParser 执行此操作并将参数打印到每个 handle_ 方法。
这个 CParser 可以通过以头的文件名作为唯一参数独立运行它来进行隔离测试。一个 DebugCParser 将被构造并用于解析标头。
CtypesParser
CtypesParser 在以下位置实现 ctypesparser.py 。它是的子类 CParser 并实现 handle_ 方法来提供对声明的更友好的ctype解释。
要使用、子类化和覆盖方法,请执行以下操作:
- HANDLE_CTYPE_CONTAINT(自身、名称、值、文件名、行号)
整型或浮点型常量(在
#define)。- HANDLE_CTYPE_TYPE_DEFINITION(SELF、NAME、CTYPE、FILENAME、LINENO)
A
typedef申报。有关类型,请参阅下面的内容ctype。- HANDLE_CTYPE_Function(self、name、rest ype、argtype、filename、lineno)
具有给定返回类型和参数列表的函数声明。
- HANDLE_CTYPE_VARIABLE(self、name、ctype、filename、lineno)
任何其他非``静态``声明。
类型由 CtypesType 。这比“真正的”ctype类型更容易操作。有一些子类用于 CtypesPointer , CtypesArray , CtypesFunction ,依此类推;有关详细信息,请参阅模块。
每个 CtypesType 类实现了 visit 方法,可以使用访问者模式样式来遍历类型层次结构。调用 visit 任何类型的方法,并实现 CtypesTypeVisitor :自动遍历所有指针、数组基、函数参数和返回类型(但不遍历结构成员)。
这在编写结构或枚举的内容时很有用。在为结构类型(它将仅由结构的标记组成)编写类型声明之前, visit 的类型和句柄。 visit_struct 方法首先打印出结构的成员。枚举也是如此。
ctypesparser.py 不能单独运行。 wrap.py 提供编写ctype包装模块的直接实现。它可以根据原始文件名过滤输出。有关用法和扩展的详细信息,请参阅模块文档字符串。