在上一节中,我们实现了注释和变量初始化的功能;在这一节中,我们来实现数组。
先来看看,我们要实现的具体功能:
int ary[5]; // 定义数组
ary[3]= 63; // 表达式给数组元素赋值
int a;
a = ary[4]; // 数组元素赋值变量
具体地讲,我们将实现:
- 具有固定大小但没有初始化列表的数组声明
- 数组索引作为赋值中的右值
- 数组索引作为赋值中的左值
数组的SysY语法是
数组定义 VarDef → Ident ‘[’ ConstExp ‘]’
同时,我们暂时只实现一维数组。
修改词法分析
我们的符号表中有标量变量(只有一个值)和函数,现在添加数组类型。同时,要在符号表中增加长度属性,表示数组的长度。
// 结构类型
enum
{
S_VARIABLE, S_FUNCTION, S_ARRAY
};
// 符号表结构体
struct symbol_table
{
char *name; // 符号名
int type; // 类型void或int
int stype; // 结构类型
int endlabel; // 函数的结束标签
int size; // 数组大小
};
同时,往add_global()
函数中添加size属性。
// 将全局变量添加到符号表,并返回符号表中的位置
int add_global(char *name, int type, int stype, int endlabel, int size)
{
/*......其余代码......*/
Tsym[y].endlabel = endlabel;
Tsym[y].size = size;
return y;
}
接下来,我们开始识别‘[’和’]’,先增加两种单词类型T_LBRACKET, T_RBRACKET
,然后在scan()
函数中,加入对它们的解析。
// 单词类型
enum
{
/*......其余类型......*/
T_COMMA, T_LBRACKET, T_RBRACKET
};
// 扫描并返回在输入中找到的下一个单词。
// 如果标记有效则返回 1,如果没有标记则返回 0
int scan(struct token *t)
{
/*......其他代码......*/
case '[': t->token = T_LBRACKET; break;
case ']': t->token = T_RBRACKET; break;
/*......其他代码......*/
}
修改语法分析
修改了add_global()
函数之后,也要在function_declaration()
函数中修改
nameslot = add_global(Text, type, S_FUNCTION, endlabel, 0);
我们通过在var_declaration()
中查看下一个单词是什么来处理标量变量声明或数组声明:
// 分析变量声明
void var_declaration(int type)
{
int id;
if(type != P_INT)
{
fprintf(stderr, "Error token != T_INT on line %d\n", Line);
exit(1);
}
while(1)
{
// 现在是标识符,如果下一个标记是"["
if (Token.token == T_LBRACKET)
{
// 跳过"["
scan(&Token);
// 获得数组的大小
if (Token.token == T_INT)
{
// 将此添加为已知数组并在汇编中生成其空间
id = add_global(Text, P_INT, S_ARRAY, 0, Token.intvalue);
arm_global_sym(id);
}
// 检查下一个"]"
scan(&Token);
match(T_RBRACKET, "]");
}
else
{
// 将其添加为已知标量并在汇编中生成其空间
id = add_global(Text, P_INT, S_VARIABLE, 0, 1);
// 如果下一个是"=",为变量赋初值
if(Token.token == T_EQU)
{
scan