总结
- 词法分析就是对字符串进行处理,然后输出对应的属性字为语法分析做准备;
- 如果对某个单词进行分析后,不属于任何属性字,那么也要按照错误处理
- 因此词法分析需要使用到许多字符串处理函数
- 语法分析就是根据给出的第一个属性字,然后预测接下来的属性字,然后读取下一个属性字,进行比较,如果和预测相比错误,那么就是语法错误
词法分析
- 汇编器要做的所有事情并不是在一次同时完成的
- 语言处理器通常是分为不同的阶段,而每个阶段都关注小的,相当简单的任务
- 这些阶段放在一起构成了一个管道,在它的不同阶段源文件都会向他的目标形式前进一步
- 一般而言,翻译任何语言的第一个阶段是词法分析,词法分析是把源文件分解成组成它的词
- 在分离和提取单词之后,词法分析器的真正工作是把单词流转变成属性字流(Token stram)
- 把单词流转换成属性字流的过程叫做属性字识别,因此词法分析器也叫做属性字识别器
//字符串流
Mov Sum, X; 执行加法运算
//单词流
MOV SUM, X
//属性字流
TOKEN_TYPE_INSTR
TOKEN_TYPE_IDENT
TOKEN_TYPE_COMMA
TOKEN_TYPE_IDENT
语法分析
- 在管道中语法分析器紧跟在词法分析器和属性字识别器后,并且有一个非常重要的任务
- 给定一个属性字流,当把它们作为整体单元时,语法分析器负责把它们拼凑在一起
- 对于函数声明的基本语法分析过程
Token CurrToken = GetNextToken();//从属性字流中读取下一个属性字
if (CurrToken == TOKEN_TYPE_FUNC) //是否是一个函数声明的开始
{
if (GetNextToken() == TOKEN_TYPE_IDENT)
{
string FuncName = GetCurrLexeme(); //当前的单词是函数名 所以保存他
if (GetNextToken() == TOKEN_TYPE_OPEN_BRACKET) {
//正确的属性字流
}
}
}
- 在对一个指令进行了语法分析之后,就可以使用指令查找标来验证它的操作数并把它转换成机器码
字符串处理库
- 空白符 空白符可以存在于任何一个字符串中,它通常被简单地定义为不可见的字符比如说空格,制表符,和和换行符
- 区分空白符是否包含换行符是很重要的。对于语句可以跨越多行的C语言中,换行没有意义,空白可以包含空白符
- 字符串与字符可以通过许多方法进行分组和分类,例如如果一个字符串的每个字符都独立满足数字字符条件,那么这个字符串就可以被看作一个数字字符串
- 你经常需要验证各种各样的字符串类型,范围从标识符到浮点数再到单个的字符,比如说大括号或双引号
- 这也是决定单词相应属性字时的公用功能,字符串处理函数库应该包含字符串分类函数的扩展集
字符串分类函数
- 汇编器完成的时候,你会发现最频繁需要的就是字符串分类函数
- 一般来说,当你按照你的方式处理源代码的时候,你需要了解所给的字符是否是下面几种中的一种
- 数字字符
- 合法标识符中的字符
- 空白符 (空格或制表符)
- 分隔符(用来分隔元素的符号,如括号,逗号等)
#define TRUE 1;
#define FALSE 0;
//判定一个字符是否是数字字符
int IsCharNumeric(char cChar) {
if (cChar >= '0' && cChar <= '9')
{
return TRUE;
}
else { return FALSE; }
}
//判定一个字符是否是空白符
int IsCharWhitespace(char cChar) {
if (cChar == ' ' || cChar == "\t")
{
return TRUE;
}
else
{
return FALSE;
}
}
//判定一个字符是否是有效标识符的部分
int IsCharIdent(char cChar) {
if ((cChar >= '0' && cChar <= '9') ||
(cChar >= 'A' && cChar <= 'Z') ||
(cChar >= 'a' && cChar <= 'z') ||
cChar == '_')
{
return TRUE;
}
else
{
return FALSE;
}
}
int IsCharDelimiter(char cChar) {
if (cChar == ';' || cChar == ',' || cChar == '"' || cChar == '[' || cChar == ']' || cChar == '{' || cChar == '}' || IsCharWhitespace(cChar))
{
return TRUE;
}
else {
return FALSE;
}
}
int IsStringInt(char* pstrString) {
if (!pstrString) return FALSE;
if (strlen(pstrString) == 0) return FALSE;
unsigned int iCurrCharIndex;
if (!IsCharNumeric(pstrString[0]) && !pstrString[0] == '-') return FALSE;
for (iCurrCharIndex = 1; iCurrCharIndex < strlen(pstrString); ++iCurrCharIndex)
{
if (!IsCharNumeric(pstrString[iCurrCharIndex]))
{
return FALSE;
}
}
return TRUE;
}
int IsStringFloat(char* pstrString) {
if (!pstrString) return FALSE;
if (strlen(pstrString) == 0) return FALSE;
unsigned int iCurrCharIndex;
for (iCurrCharIndex = 0; iCurrCharIndex < strlen(pstrString); iCurrCharIndex++)
{
if (!IsCharNumeric(pstrString[iCurrCharIndex])&&!(pstrString[iCurrCharIndex]=='.')&&!(pstrString[iCurrCharIndex]=='-'))
{
return FALSE;
}
}
//小数点
int iRadixPointFound = FALSE;
for (iCurrCharIndex = 0; iCurrCharIndex < strlen(pstrString); iCurrCharIndex++)
{
if (pstrString[iCurrCharIndex]=='.')
{
if (iRadixPointFound) { return FALSE; }
else
{
iRadixPointFound = TRUE;
}
}
}
for (iCurrCharIndex = 1; iCurrCharIndex < strlen(pstrString); iCurrCharIndex++)
{
if (pstrString[iCurrCharIndex]=='-')
{
return FALSE;
}
}
if (iRadixPointFound) {
return TRUE;
}
else {
return FALSE;
}
}
int IsStringWhitespace(char* pstrString) {
if (!pstrString) return FALSE;
if (strlen(pstrString) == 0) return TRUE;
for (unsigned int iCurrCharIndex = 0; iCurrCharIndex < strlen(pstrString); iCurrCharIndex++)
{
if (!IsCharWhitespace( pstrString[iCurrCharIndex]))
{
return FALSE;
}
}
return TRUE;
}
int IsStringIdent(char* pstrString) {
if (!pstrString) return FALSE;
if (strlen(pstrString) == 0) return FALSE;
if (pstrString[0] >= '0' && pstrString[0] <= '9') return FALSE;
for (unsigned int iCurrCharIndex = 0; iCurrCharIndex < strlen(pstrString); iCurrCharIndex++)
{
if (!IsCharIdent(pstrString[iCurrCharIndex]))
{
return FALSE;
}
}
return TRUE;
}
汇编程序
- 汇编程序主要是由管理各种脚本所定义元素如变量,函数和标签的表组成
- 由于各个脚本之间的这些元素的数量都各不相同,对于他们当中的大部分表,可以使用链表来增长到需要的大小
- 我决定在内存中缓存所有的东西,这样会使处理过程更快速
- 缓冲将要输出的汇编指令流,
词法分析器的接口
- int GetNextToken()
- 返回当前节点的属性字并把当前节点向后移动一个节点,还要填入g_Lexer结构来反映所有的当前属性字的信息
- 当我们对单词进行分析的时候,因为分隔符本身也是属性字的一种,所以对于Index0,当遍历到其不是空白符或者制表符的时候就停止,然后让Index1等于Index0,再让Index1进行累加遍历,如果此时Index1遇到分隔符就停止,这样就可以分离出来分隔符。但是其长度为0,所以要让index1加一。
- 不过此时会有一个bug," " "
- char * GetCurrLexeme()
- 返回一个字符指针,它指向包含当前单词的字符串,什么是g_Tokenizer
- GetLookAheadChar()
- 向前查看,查看位于当前属性字之后的那个属性字的处理过程。虽然他的确读取了字符,但是它并没有把属性字流的当前指针向后移动。
- 如果当前读取内容不足以让你正确决定余下的属性字应该是什么,在这些情况下使用向前查看
Var MyVar Var MyVar[256] //不确定性
- void SkipToNextLine()
- 仅仅想忽略掉一整行的属性字,源代码在内部存储的时候被看作一系列单独的行,这个函数就是增加当前行的计数并且重置属性字识别器的位置
- ResetLexer()
- 重置一切,词法分析器要对源代码执行两次遍历,每次执行之前都需要重置,这个函数只会被使用两次
错误处理
- 错误处理主要包括三个方面,检测,重新同步和消息输出
- 检测是判断错误是什么时候发生的,它是什么类型的错误
- 重新同步是使语法分析器回滚的处理过程,让程序可以标记多重错误
- 错误消息必须输出到屏幕或是某种类型的日志文件
语法分析
- 重点在于识别初始的属性字,并且根据那个初始的属性字是如何适合语言的规则的,
- 来预知它后面应该跟随什么属性字
- 根据这些初始的属性字,你可以判断你正在处理什么种类的代码