包装类型

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 提供一个简单的子类 CtypesParserCtypesWrapper ,它将找到的ctype声明以可以作为模块导入的格式写入文件。

解析器修改

解析器基于修改后的版本构建 PLY ,lex和yacc的一个Python实现。修改后的源代码包含在 wraptypes 目录。有关的修改如下:

  • 语法是从Parser中抽象出来的,因此可以很容易地在同一模块中定义多种语法。

  • 标记和符号跟踪它们的文件名和行号。

  • 词法分析器状态可以推送到堆栈上。

第一次运行解析器(或在修改解析器之后)时,PLY将创建 pptab.pyparsetab.py 在当前目录中。这些是生成的状态机,可能需要几秒钟才能生成。档案 parser.out 如果启用了调试,则创建,并包含(生成的最后一个解析器的)解析器描述,这对于调试是必不可少的。

预处理器

语法和解析器在中定义 preprocessor.py

只有一个词法分析器状态。每个令牌都有一个字符串类型(例如 'CHARACTER_CONSTANT' )和值。直接从源文件读取时,令牌值仅为永远字符串。将令牌写入输出列表时,它们有时具有元组值(例如, PP_DEFINE 输出上的令牌)。

定义了两个词法分析器类: PreprocessorLexer ,它读取一堆文件(实际上是字符串)作为输入,以及 TokenListLexer ,它从已解析的令牌列表中读取(用于解析表达式)。

预处理入口点是 PreprocessorParser 班级。这将创建一个 PreprocessorLexer 以及它在构造过程中的语法。默认情况下,系统包含路径包括GCC搜索路径,但可以通过更改 include_pathframework_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_declarativesenable_elif_conditionals 如果最顶层的 ExecutionState 允许声明和 #elif 要分别解析的条件句。执行状态堆栈使用 condition_* 方法:研究方法。

PreprocessorParser 有一个 PreprocessorNamespace 它跟踪当前定义的宏。您可以创建和指定自己的命名空间,也可以使用默认情况下创建的命名空间。默认命名空间包括解析系统头所需的GCC平台宏,以及一些STDC宏。

将令牌写入输出列表以及分析条件表达式时,将展开宏。 PreprocessorNamespace.apply_macros(tokens) 解决了这一问题,替换了函数参数、变量参数、宏对象,并(主要)避免了无限递归。它还不能处理 ### 运算符,它们是解析Windows系统标头所需的。

计算条件的过程 (#if#elif )是:

  1. 之间的令牌 PP_IFPP_ELIFNEWLINE 被扩展为 apply_macros

  2. 生成的令牌列表用于构造 TokenListLexer

  3. 此词法分析器用作 ConstantExpressionParser 。此解析器使用 ConstantExpressionGrammar ,这构建了一个 ExpressionNode 物体。

  4. parse 对象上调用 ConstantExpressionParser ,它返回结果的顶层 ExpressionNode ,或 None 如果存在语法错误。

  5. 这个 evaluate 的方法。 ExpressionNode 使用预处理器的命名空间作为求值上下文进行调用。这允许表达式节点解析 defined 操作员。

  6. 其结果是 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

的词法分析器 CParserCLexer 将预处理器输出的令牌列表作为输入。特殊的预处理器令牌,如 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 (类型对象), storagedeclarator

类型

类型有一个列表, qualifiers (例如,‘const’、‘Volatile’等)和 specifiers (肉质的一部分)。

TypeSpecifier

基本类型说明符只是一个字符串,例如 'int''Foo''unsigned' 。请注意,类型可以有多个类型说明符;并非所有组合都有效。

StructTypeSpecifier

这是结构或联合的说明符(如果 is_union 为True)类型。 tag 提供可选的 foo 在……里面 struct foodeclarations 是肉(不透明或未指定结构的空列表)。

EnumSpecifier

这是枚举类型的说明符。 tag 提供可选的 foo 在……里面 enum fooenumerators 是枚举器对象的列表(未指定枚举的空列表)。

枚举器

枚举数仅存在于EnumSpecifier中。包含 nameexpression ,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类型更容易操作。有一些子类用于 CtypesPointerCtypesArrayCtypesFunction ,依此类推;有关详细信息,请参阅模块。

每个 CtypesType 类实现了 visit 方法,可以使用访问者模式样式来遍历类型层次结构。调用 visit 任何类型的方法,并实现 CtypesTypeVisitor :自动遍历所有指针、数组基、函数参数和返回类型(但不遍历结构成员)。

这在编写结构或枚举的内容时很有用。在为结构类型(它将仅由结构的标记组成)编写类型声明之前, visit 的类型和句柄。 visit_struct 方法首先打印出结构的成员。枚举也是如此。

ctypesparser.py 不能单独运行。 wrap.py 提供编写ctype包装模块的直接实现。它可以根据原始文件名过滤输出。有关用法和扩展的详细信息,请参阅模块文档字符串。