實現簡易的C語言編譯器(part 2)

? ? ? ? 在上一部分中,我們分析并實現了詞法分析的過程。這一部分,我們從頭文件和宏定義兩個方面入手,來分析前處理過程。

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 10)
實現簡易的C語言編譯器(part 11)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容