包装类型

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 )是:

  1. 之间的令牌 PP_IF 或 PP_ELIF 和 NEWLINE 被扩展为 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

的词法分析器 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包装模块的直接实现。它可以根据原始文件名过滤输出。有关用法和扩展的详细信息,请参阅模块文档字符串。