安排 lex 和解析文件的程序方法是什么?
What is the procedural way to arrange lex and parse files?
我搜索了很多,似乎仍然无法掌握令牌的工作流程和文件排列、词法分析器和解析器。我在 Visual Studio 中使用纯 C。浏览了无数教程,他们似乎跳过了这一部分。
所以当你在 source.txt 中编写一个简单函数时,词法分析器在 C
文件中,读取源文件,并将简单函数分解为标记?
在 Dr. Dobbs 的书中,有一个包含已定义标记列表的文件被写入 def 文件,如果前一个问题是正确的,那么这个预定义标记列表适合放在哪里?
一旦源文件被lex
读取,并且标记被拆分,解析器如何检索lex
输出的标记化信息,然后制作语法?
我见过 typedef 结构用于预定义标记的轻微分解,以及内部的 lex。 1 个文件中的标记、1 个中的 lex 和 1 个解析器也是如此吗?或者 1 中的标记,但 lex 和 parse 一起调用该标记文件?
我只需要澄清一下,这是正确方向的一个很好的观点,非常感谢任何帮助。
是的,你引用的第二个例子和理解是很典型的一次调整。
是的,pre-defined 令牌列表(如果允许,通常在枚举中)存在于 header 或类似列表中。 lex 和解析器都可以引用那个 header 文件。
但是 'lex' 发现 的标记在执行时被传递给解析器。
组织这些任务的一种简单经典方法是将各个部分设置为相互传递数据结构的子例程。请原谅我草率的语法。
首先你需要一个由词法分析器生成的标记定义。这几乎总是一个带有枚举的结构,用于指示哪种令牌类型,以及一个联合类型,用于携带令牌类型可能具有的任何值:
struct Token {
enum // distinguishes token types
{ EndOfFile, Integer, String, Float, Identifier
Semicolon, Colon, Plus, Minus, Times, Divide, LefParen, RightParen,
KeywordBegin, KeywordDeclare, KeywordEnd, ...
} tokentype
union {
long numeric_value; // holds numeric value of integer-valued token
char* string_value; // holds ptr to string body of identifiers or string literals
float float_value; // holds numeric value of floating-point valued token
} tokenvalue
}
您需要构建一个抽象语法树。为此,您需要一个 TreeNode 类型。
像令牌一样,这些几乎总是一个枚举来指示哪个节点类型,以及一个联合来保存节点类型的各种属性,最后是指向子节点的指针列表:
struct TreeNode {
enum // distiguishes tree node types
{ Goal, Function, StatementList, Statement, LeftHandSide, Expression,
Add, Subtract, Times, Divide, Identifier, FunctionCall, ...
} nodetype
children* TreeNode[4]; // a small constant here is almost always enough
union // hold values specific to node type, often includes a copy of lexer union
{ long numeric_value; // holds:
// numeric value of integer-valued token
// index of built-in function number
// actual number of children if it varies
// ...
char* string_value; // holds ptr to string body of identifiers or string literals
float float_value; // holds numeric value of floating-point valued token
} nodevalue
}
MyCompiler.c 包含以下代码:
int main() {
filehandle Handle = OpenSourceFile(&FileName);
ASTrootnode TreeNode = Parser(Handle);
CodeGenerator(ASTrootnode);
exit();
}
Parser.c 包含以下代码:
TreeNode Parser(filehandle Handle) {
<parsingmachinery>
nexttoken Token=Lexer(filehandle);
<moreparsingmachinery to build tree nodes>
return toplevel_TreeNode;
}
Lexer.c 包含以下代码:
Token Lexer(filehandle Handle) {
token Token;
<process characters in buffer>
if bufferp=end_of_buffer
fileread(filehandle,&bufferbody,bufferlength);
<process characters in buffer>
token.tokentype=<typeofrecognizedtoken>
token.tokenvalue.long=<valueofnumerictoken>
...
return Token;
}
您显然希望将 Token 和 TreeNode 声明放入可在编译器源文件之间共享的头文件中。
如果您构建一个高性能编译器,您将需要优化这些例程。一个简单的例子:FileHandle 可以成为一个全局变量,因此不需要作为显式参数在各个部分之间传递。一个不那么简单的例子:你会想要一个高性能的词法分析器生成器,或者手动编码词法分析器,以最大限度地提高其字符处理率,尤其是在跳过空格和注释时。
如果您想查看有关如何构建构建 AST 的解析器的具体细节,请参阅我关于构建递归下降解析器的 SO 回答:
我搜索了很多,似乎仍然无法掌握令牌的工作流程和文件排列、词法分析器和解析器。我在 Visual Studio 中使用纯 C。浏览了无数教程,他们似乎跳过了这一部分。
所以当你在 source.txt 中编写一个简单函数时,词法分析器在 C
文件中,读取源文件,并将简单函数分解为标记?
在 Dr. Dobbs 的书中,有一个包含已定义标记列表的文件被写入 def 文件,如果前一个问题是正确的,那么这个预定义标记列表适合放在哪里?
一旦源文件被lex
读取,并且标记被拆分,解析器如何检索lex
输出的标记化信息,然后制作语法?
我见过 typedef 结构用于预定义标记的轻微分解,以及内部的 lex。 1 个文件中的标记、1 个中的 lex 和 1 个解析器也是如此吗?或者 1 中的标记,但 lex 和 parse 一起调用该标记文件?
我只需要澄清一下,这是正确方向的一个很好的观点,非常感谢任何帮助。
是的,你引用的第二个例子和理解是很典型的一次调整。
是的,pre-defined 令牌列表(如果允许,通常在枚举中)存在于 header 或类似列表中。 lex 和解析器都可以引用那个 header 文件。
但是 'lex' 发现 的标记在执行时被传递给解析器。
组织这些任务的一种简单经典方法是将各个部分设置为相互传递数据结构的子例程。请原谅我草率的语法。
首先你需要一个由词法分析器生成的标记定义。这几乎总是一个带有枚举的结构,用于指示哪种令牌类型,以及一个联合类型,用于携带令牌类型可能具有的任何值:
struct Token {
enum // distinguishes token types
{ EndOfFile, Integer, String, Float, Identifier
Semicolon, Colon, Plus, Minus, Times, Divide, LefParen, RightParen,
KeywordBegin, KeywordDeclare, KeywordEnd, ...
} tokentype
union {
long numeric_value; // holds numeric value of integer-valued token
char* string_value; // holds ptr to string body of identifiers or string literals
float float_value; // holds numeric value of floating-point valued token
} tokenvalue
}
您需要构建一个抽象语法树。为此,您需要一个 TreeNode 类型。 像令牌一样,这些几乎总是一个枚举来指示哪个节点类型,以及一个联合来保存节点类型的各种属性,最后是指向子节点的指针列表:
struct TreeNode {
enum // distiguishes tree node types
{ Goal, Function, StatementList, Statement, LeftHandSide, Expression,
Add, Subtract, Times, Divide, Identifier, FunctionCall, ...
} nodetype
children* TreeNode[4]; // a small constant here is almost always enough
union // hold values specific to node type, often includes a copy of lexer union
{ long numeric_value; // holds:
// numeric value of integer-valued token
// index of built-in function number
// actual number of children if it varies
// ...
char* string_value; // holds ptr to string body of identifiers or string literals
float float_value; // holds numeric value of floating-point valued token
} nodevalue
}
MyCompiler.c 包含以下代码:
int main() {
filehandle Handle = OpenSourceFile(&FileName);
ASTrootnode TreeNode = Parser(Handle);
CodeGenerator(ASTrootnode);
exit();
}
Parser.c 包含以下代码:
TreeNode Parser(filehandle Handle) {
<parsingmachinery>
nexttoken Token=Lexer(filehandle);
<moreparsingmachinery to build tree nodes>
return toplevel_TreeNode;
}
Lexer.c 包含以下代码:
Token Lexer(filehandle Handle) {
token Token;
<process characters in buffer>
if bufferp=end_of_buffer
fileread(filehandle,&bufferbody,bufferlength);
<process characters in buffer>
token.tokentype=<typeofrecognizedtoken>
token.tokenvalue.long=<valueofnumerictoken>
...
return Token;
}
您显然希望将 Token 和 TreeNode 声明放入可在编译器源文件之间共享的头文件中。
如果您构建一个高性能编译器,您将需要优化这些例程。一个简单的例子:FileHandle 可以成为一个全局变量,因此不需要作为显式参数在各个部分之间传递。一个不那么简单的例子:你会想要一个高性能的词法分析器生成器,或者手动编码词法分析器,以最大限度地提高其字符处理率,尤其是在跳过空格和注释时。
如果您想查看有关如何构建构建 AST 的解析器的具体细节,请参阅我关于构建递归下降解析器的 SO 回答: