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