变量这玩意儿本身很简单,无非是一个标识符开头,然后若干对中间夹着一个算数运算的正反方括号,而分析算术运算节点的任务又可以委托给专门的分析器来做,因此识别变量的分析器本身其实没什么需要做的。下面是它的数据结构。
struct VariableAnalyser {
memberSyntaxAnalyser
struct VariableNode* var; // 用来存放识别的变量
};
下面是它的各成员函数的实现,这里用了一种有趣的实现方式。
/* variable-analyser.c */
#include<stdlib.h>
#include"datastruct.h"
#include"syntax-analyser.h"
#include"syntax-node.h"
#include"COOL/MemoryAllocationDebugger.h"
extern struct Stack* analyserStack;
extern struct SyntaxAnalyser* newOperationAnalyser(void);
#define wrapname(name) name ## _VariableAnalyser
void wrapname(consumeIdentifier)(void*, struct Token*);
void wrapname(consumeLBracket)(void*, struct Token*);
void wrapname(consumeRBracket)(void*, struct Token*);
void wrapname(consumeIdentifier)(void* self, struct Token* t)
{
((struct VariableAnalyser*)self)->var = newVariableNode(t->image);
((struct VariableAnalyser*)self)->consumeToken = wrapname(consumeLBracket);
}
void wrapname(consumeLBracket)(void* self, struct Token* t)
{
if(LBRACKET == t->type) {
analyserStack->push(analyserStack, newOperationAnalyser());
} else {
// printf("Varaible analyser returns.\n");
struct VariableNode* var = ((struct VariableAnalyser*)self)->var;
revert(analyserStack->pop(analyserStack));
struct SyntaxAnalyser* analyser = analyserStack->peek(analyserStack);
analyser->consumeNonTerminal(analyser, (struct AbstractSyntaxNode*)var);
analyser = analyserStack->peek(analyserStack);
analyser->consumeToken(analyser, t);
}
}
void wrapname(consumeRBracket)(void* self, struct Token* t)
{
if(RBRACKET == t->type) {
((struct VariableAnalyser*)self)->consumeToken =
wrapname(consumeLBracket);
} else {
printf("Expecting `]', but %s\n", t->image);
// TODO 错误处理
}
}
void wrapname(consumeNonTerminal)(void* self, struct AbstractSyntaxNode* op)
{
struct VariableAnalyser* varAna = (struct VariableAnalyser*)self;
varAna->var->dimInfor->enqueue(varAna->var->dimInfor, op);
varAna->consumeToken = wrapname(consumeRBracket);
}
struct SyntaxAnalyser* newVariableAnalyser(void)
{
struct VariableAnalyser* varAna = (struct VariableAnalyser*)
allocate(sizeof(struct VariableAnalyser));
varAna->consumeToken = wrapname(consumeIdentifier);
varAna->consumeNonTerminal = wrapname(consumeNonTerminal);
return (struct SyntaxAnalyser*)varAna;
}
#undef wrapname
你一定还记得在OperationAnalyser的实现中,为了区分当前需要的是一个运算符还是一个数,它的consumeToken函数指向一个分流函数,该分流函数根据当前OperationAnalyser的needFactor成员判断是该调用consumeFactor还是consumeOperator,那是一种不好的实现:本应该可以用多态来实现的部分,却滥用分支语句,这在设计模式上是有问题的。现在VariableAnalyser纠正了这一点。在一个VariableAnalyser刚刚新建时,它的consumeToken函数指向wrapname(consumeIdentifier)函数,然后在该函数内将自身的consumeToken函数改指向wrapname(consumeLBracket)函数。其它的函数中也可以看到类似的小动作。这些小动作使设计更清晰了。
附:一个用来测试语法分析模块的驱动器
当然,这并不意味着语法分析结束了,还有错误处理没有完成呢。
这个是语法分析器头文件。记得把分析器结构定义放进 datastruct.h 中。
/* syntax-analyser.h */
#ifndef _SYNTAX_ANALYSER_H
#define _SYNTAX_ANALYSER_H
#include"datastruct.h"
struct SyntaxAnalyser* newOperationAnalyser(void);
void initialLRStates(void);
void destructLRStates(void);
struct SyntaxAnalyser* newLRAnalyser(void);
struct SyntaxAnalyser* newVariableAnalyser(void);
#ifdef _DEBUG_MODE
#include"operation-analyser.c"
#include"lr-analyser.c"
#include"variable-analyser.c"
#endif /* _DEBUG_MODE */
#endif /* _SYNTAX_ANALYSER_H */
下面这个东西脱胎于OperationAnalyser的测试驱动器。不过也没有处理行号,你可以结合词法分析的测试来进行修改。
#include<stdio.h>
#ifndef _DEBUG_MODE
#define _DEBUG_MODE
#endif /* _DEBUG_MODE */
#include"COOL/MemoryAllocationDebugger.h"
#include"datastruct.h"
#include"syntax-node.h"
#include"syntax-analyser.h"
#include"dfa.h"
#include"dfa.c"
FILE* treeout;
struct FakeDefaultAnalyser {
memberSyntaxAnalyser
};
struct SyntaxAnalyser* newFakeDefaultAnalyser(void);
void FakeDefaultAnalyser_ConsumeNT(void*, struct AbstractSyntaxNode*);
void FakeDefaultAnalyser_ConsumeT(void*, struct Token*);
struct Stack* analyserStack; // 分析器栈
FILE* input; // 输入文件
// 存放终结符
char buffer[64];
struct Token token = {
0, SKIP, NULL, buffer
};
int main()
{
treeout = fopen("syntax.xml", "w"); // 输出为xml
input = fopen("testin", "r");
analyserStack = newStack();
analyserStack->push(analyserStack, newFakeDefaultAnalyser());
initialLRStates();
tokenize();
destructLRStates();
printf("Test done.\n");
revert(analyserStack->pop(analyserStack));
analyserStack->finalize(analyserStack);
showAlloc;
fclose(input);
fclose(treeout);
return 0;
}
struct SyntaxAnalyser* newFakeDefaultAnalyser(void)
{
struct SyntaxAnalyser* defAna = (struct SyntaxAnalyser*)
allocate(sizeof(struct FakeDefaultAnalyser));
defAna->consumeToken = FakeDefaultAnalyser_ConsumeT;
defAna->consumeNonTerminal = FakeDefaultAnalyser_ConsumeNT;
return defAna;
}
void FakeDefaultAnalyser_ConsumeNT(void* self, struct AbstractSyntaxNode* node)
{
printf("\nReturned.\n");
if(NULL == node) {
fprintf(treeout, "NULL returned.\n");
} else {
fprintf(treeout, "<Jerry>\n");
node->printree(node, 1);
fprintf(treeout, "</Jerry>\n");
node->delNode(node);
}
}
void FakeDefaultAnalyser_ConsumeT(void* self, struct Token* t)
{
if(NULL == t->image) {
printf("Token passed: %2d / NULL image\n", t->type);
} else {
printf("Error before %s\n", t->image);
exit(1);
}
}
int nextChar(void)
{
return fgetc(input);
}
struct Token* firstToken(void)
{
analyserStack->push(analyserStack, newLRAnalyser());
return &token;
}
struct Token* nextToken(void)
{
if(SKIP != token.type) {
struct SyntaxAnalyser* analyser = (struct SyntaxAnalyser*)
(analyserStack->peek(analyserStack));
analyser->consumeToken(analyser, &token);
}
return &token;
}
void eofToken(void)
{
nextToken();
struct SyntaxAnalyser* analyser = (struct SyntaxAnalyser*)
(analyserStack->peek(analyserStack));
struct Token endToken = {0, END, NULL, NULL};
analyser->consumeToken(analyser, &endToken);
}
分享到:
相关推荐
词法分析器的任务是从源代码文本中识别出一个个独立的、有意义的单元,这些单元被称为“记号”或“token”。例如,在Java程序中,记号可能包括关键字(如`public`,`class`),标识符(如变量名、类名),运算符(如...
Java的词法分析器需要识别关键字、标识符、常量、运算符等。 2. **文法定义**:项目可能包含一个形式化的Java语法规则定义,例如使用EBNF(扩展巴科斯范式)。 3. **抽象语法树(AST)**:构建AST是语法分析的关键...
" 这条语句在语法分析器的作用下,会被解析为一个声明语句,包括类型“int”,变量名“x”,赋值运算符“=”以及数值常量“5”。语法分析器通过递归下降、LL解析、LR解析、LALR解析等多种方法来实现这一过程。 在...
语法分析器需要根据语言的语法规则来识别语句的结构,例如,在 C 语言中,一个基本的语句结构是“变量声明”,它由“变量名”、“赋值符号”和“初值”组成。 在本文中,我们将使用 LittleP+ 文法来描述语法分析器...
实验内容包括选取常见的高级语言语法结构(如表达式、条件语句、循环语句等),定义它们的文法规则,并实现一个能够识别这些规则的语法分析器。输入是源程序文件,输出是对源程序的合法性判断,合法则输出"success...
词法分析器通常是通过正则表达式实现的,它可以识别并分离出源代码中的不同部分。 然后,语法分析器开始工作。最常用的是上下文无关文法(CFG,Context-Free Grammar)的解析方法,如LR(Left-to-Right,Rightmost ...
语法分析器通常使用这些文法来识别和构建程序的结构。 4. **解析技术** - **递归下降解析**:一种简单但有限的解析方法,适用于文法规则较简单的语言。 - **LR解析**:自左向右扫描输入,采用右most衍生来构建...
在Java中,语法分析器会确保代码符合Java语言的语法规则,如类定义、方法声明、变量赋值等。如果代码符合语法规则,语法分析器会生成抽象语法树(Abstract Syntax Tree, AST),这是对源代码的一种结构化表示。 3. ...
本项目的目标是构建一个能够识别并解析Tiny语言源代码的语法分析器。 ##### 2. 词法分析与语法分析 - **词法分析**:词法分析是编译过程的第一步,它负责将源代码转换为一系列的标记(Token)。在这个过程中,源...
LR语法分析器是编译原理中的一种重要技术,用于分析和识别源代码中的语法结构。 LR语法分析器的原理 LR语法分析器是基于上下文无关语法(Context-Free Grammar)和左递归消除(Left Recursion Elimination)技术...
在类C语言的词法语法分析器中,生成的中间代码可能包括操作符、操作数和临时变量,表示如“a = b + c”这样的表达式。四元码通常包含四个元素:操作、操作数1、操作数2和结果,例如:“ADD r1, r2, r3”,其中r1是...
词法分析器(也称为扫描器或lexer)首先读取源代码,识别并生成一个个称为标记(tokens)的最小语法单元。这些标记代表了诸如变量名、关键字、操作符等语言元素。接着,语法分析器(也称作解析器)基于这些标记,...
以C++类声明为例,语法分析器需要识别出"class"关键字,接着是类名,可能的基类列表,然后是成员变量和成员函数的定义。在这个过程中,分析器需要不断检查当前输入是否符合预定的语法规则,并逐步构建出抽象语法树...
《Windows下词法分析分析器Flex和语法分析器Bison的使用详解》 在计算机科学领域,词法分析和语法分析是编译器设计的重要环节。Flex和Bison是两个强大的开源工具,分别用于词法分析和语法分析,尤其在Windows环境下...
例如,对于C语言,保留字如"if"、"else",标识符如变量名,常数如整型或浮点型数值,运算符如"+"、"-",以及分隔符如括号、逗号等,都是词法分析器需要识别的对象。通过词法分析器,源代码被转换成一系列有意义的...
词法分析器根据预定义的词法规则(正则表达式或正规文法)识别这些元素,为后续的语法分析提供基础。在本案例中,词法分析器的实现可能基于状态转换图,通过逐字符扫描源代码并根据规则进行状态转移,识别并输出对应...
词法分析器负责识别源代码中的词汇单元,而语法分析器则根据这些词汇单元构建出程序的抽象语法树(AST)。以下是对这两个关键组件的详细解释: **词法分析器(Lexer)** 词法分析器是编译器的第一个阶段,它将源...
例如,在处理C++程序时,词法分析器会识别出关键字(如`int`、`if`)、标识符(如变量名)、操作符(如`+`、`-`)以及常量(如数字、字符串)等。在给定的压缩包文件中,"Scanner"可能包含的就是实现词法分析器的...
例如,Java的语义分析器会检查变量是否在使用前已被正确声明,操作数类型是否匹配运算符,以及方法调用的参数数量和类型是否正确。在Java中,类加载器和类型系统也是语义分析的重要组成部分。 **编译原理在Java中的...