在上一部分中,我们分析并实现了词法分析的过程。这一部分,我们从头文件和宏定义两个方面入手,来分析前处理过程。
2.1 头文件
让我们先来看一段代码:
#include "stdio.h"
int main(int argc, char* argv[])
{
printf("Hello World!");
return 0;
}
在这段代码中,添加头文件的目的是为了引入函数int printf(char *fmt, ...);
,也就是说大可以将上面代码改写成:
int printf(char *fmt, ...);
int main(int argc, char* argv[])
{
printf("Hello World!");
return 0;
}
理论上,经过替换后的这段代码编译没有任何问题,而这就是头文件替换的目的。但是,在没有编译之前,我们根本不知道这个函数里面调用了头文件的什么东西,可能是变量定义,也可能是某个或者多个函数,或者只是习惯性地包含了这个头文件。所以,我们需要将头文件内所有的内容全部展开到当前文件中来。这就非常好实现,只需要记住头文件的名字然后打开对应的文件,然后复制到头文件所在的地方就可以了。Python实现如下:
def modules(self):
result = ''
while self.current_char is not None and self.current_char.isalnum():
result += self.current_char
self.advance()
if result == 'include':
file_name = self.read_lines().strip()
file_name = file_name.lstrip('<')
file_name = file_name.rstrip('>')
file_name = file_name.replace(' ', '')
# current file path
file_dir = os.path.dirname(os.path.realpath(__file__))
file_name = os.path.join(file_dir, file_name)
try:
with open(file_name, 'r') as f:
new_text = f.read()
new_text += self.text[self.pos:]
self.text = new_text
self.pos = 0
except IOError:
raise Exception()
return self.get_next_token()
头文件引用里存在一个概念:循环依赖。比如,对于"car.h"和"wheel.h"两个文件。
// car.h
#include "wheel.h"
struct Car {
struct Wheel wheels[4];
...
};
// wheel.h
#include "car.h"
struct Wheel {
struct Car car;
...
};
由于两个文件内容相互包含,按照头文件展开的原则,势必会是一个死循环。可以采用前向声明加以避免,但是编译器必须能够及时检测出来。只需要增加一个对头文件内部的头文件计数上的辅助判断即可。
2.2 宏定义
C语言宏定义的规则为:
#define macro_name macro_string
定义之后,会用macro_string
去替换代码中所有的macro_name
。但是,由于macro_string
可以是数字、表达式和函数,不单纯只是替换。还是从代码分析入手,先看下面这段代码中的宏定义及其使用:
#define FOO 0
#define ADD(X, Y) (X + Y)
int main()
{
return ADD(1, 2) - FOO;
}
对于FOO
这个宏定义,只需要将后面代码中的同名标志符直接用0
替换即可;而对于ADD
这种函数式的宏,还需要进行实参的替换。最终,经过替换后的代码为:
int main()
{
return (1 + 2) - 0;
}
这样就实现了前处理过程。
我们使用python中的正则表达式进行内容的替换。其中,宏定义名、待替换的字符串和被替换的字符串分别如下:
macro_name : r'\b\w+(?=[(\s])'
macro_string : r'(?<=[)]).+' , macro_string
replaced_string : r'\bmacro_string[^\w]'
这里会用到一个判断,即是否是函数式的宏定义。是通过判断宏定义名后是否紧接着括号来判断的。如果是普通宏定义,进行完整替换;否则,还需要逐个提取参数,进行参数传递,然后进行替换。完整的代码如下:
def macros(self):
result = ''
while self.current_char is not None and self.current_char.isalnum():
result += self.current_char
self.next()
if result == 'define':
literals = self.read_lines().strip()
marco_name_pattern = re.compile(r'\b\w+(?=[(\s])')
result = re.search(marco_name_pattern, literals)
if result is None:
return None
# obtain the macro name
macro_name = result.group(0)
# obtain the macro string
rest_literals = literals[len(macro_name):]
if rest_literals[0] == '(':
defns_pattern = re.compile(r'(?<=[)]).+')
result = re.search(defns_pattern, rest_literals)
if result is None:
return None
defns = result.group(0)
else:
defns = rest_literals
rest_literals = rest_literals[:len(rest_literals)-len(defns)]
args_list = None
if not rest_literals == '':
args_list = self.extract_args(rest_literals)
# replaced identifier
arg_str = macro_name
if args_list is not None:
arg_str += '\('
for i in range(len(args_list)):
if i < len(args_list) - 1:
arg_str += '\w+,[\s]*'
else:
arg_str += '\w+'
arg_str += '\)'
# match the macro in the text
macro_pattern = r'\b%s[^\w]' % arg_str
original_str = self.text[self.pos:]
result = re.findall(macro_pattern, original_str)
if len(result) > 0:
for node in result:
macro_defns = defns
node_str = node[len(macro_name)+1:len(node)-1]
parms_list = self.extract_args(node_str)
for k in range(len(parms_list)):
macro_defs_parm_pattern = re.compile(r'\b%s\b' % args_list[k])
macro_defns = re.sub(macro_defs_parm_pattern, '%s' % parms_list[k], macro_defns)
replaces_str = ' {}{}'.format(macro_defns, node[-1])
result = re.sub(macro_pattern, replaces_str, original_str, 1)
# reset the text
self.text = result
original_str = self.text
self.pos = 0
return self.get_next_token()
这里用到的一个提取参数的辅助函数:
def extract_args(literal):
literal = literal.lstrip('(')
literal = literal.rstrip(')')
literal = literal.replace(' ', '')
args_pattern = re.compile(r'(?<=,)?(\w+)(?=,)?')
args_list = re.findall(args_pattern, literal)
return args_list
也是用正则表达式进行提取,为:
arg_list : r'(?<=,)?(\w+)(?=,)?'
至此,前处理过程就分析完毕。我们已经过掌握了处理最常用宏定义的过程。至于其它的前处理过程,如预编译指令的内容,这里将不再涉及。下一部分,我们进入语法分析的内容。
实现简易的C语言编译器(part 0)
实现简易的C语言编译器(part 1)
实现简易的C语言编译器(part 3)
实现简易的C语言编译器(part 4)
实现简易的C语言编译器(part 5)
实现简易的C语言编译器(part 6)
实现简易的C语言编译器(part 7)
实现简易的C语言编译器(part 8)
实现简易的C语言编译器(part 9)
网友评论