美文网首页C语言
实现简易的C语言编译器(part 2)

实现简易的C语言编译器(part 2)

作者: 为了忘却的程序猿 | 来源:发表于2019-07-17 23:00 被阅读0次

        在上一部分中,我们分析并实现了词法分析的过程。这一部分,我们从头文件和宏定义两个方面入手,来分析前处理过程。

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)

相关文章

网友评论

    本文标题:实现简易的C语言编译器(part 2)

    本文链接:https://www.haomeiwen.com/subject/ejvpkctx.html