实现一个简单的C语言编译器是一项复杂但非常有意义的任务。它可以帮助我们深入了解编译器的工作原理,包括词法分析、语法分析、语义分析以及代码生成等阶段。下面我们将逐步介绍如何用C语言实现一个简单的编译器,主要针对一个非常基础的子集语言(例如只支持基本的算术表达式)。
一个典型的编译器可以分为以下几个阶段:
为了简化实现,我们将专注于前三个阶段(词法分析、语法分析和语义分析),并生成伪汇编代码作为输出。
词法分析器的任务是将输入的源代码分解成一个个标记(tokens)。例如,对于输入 a = 3 + 4;
,词法分析器会将其分解为以下标记序列:
以下是实现词法分析器的代码示例:
#include <stdio.h>
#include <ctype.h>
#include <string.h>
typedef enum {
TOKEN_EOF,
TOKEN_IDENTIFIER,
TOKEN_INTEGER,
TOKEN_PLUS,
TOKEN_MINUS,
TOKEN_MULTIPLY,
TOKEN_DIVIDE,
TOKEN_ASSIGNMENT,
TOKEN_SEMICOLON
} TokenType;
typedef struct {
TokenType type;
char value[100];
} Token;
Token current_token;
void error(const char *msg) {
fprintf(stderr, "Error: %s\n", msg);
exit(1);
}
void skip_whitespace() {
while (isspace(current_token.value[0])) {
current_token.value[0] = getchar();
}
}
Token get_next_token() {
skip_whitespace();
if (isalpha(current_token.value[0])) {
int i = 0;
while (isalnum(current_token.value[i] = getchar())) {
i++;
}
current_token.value[i] = '\0';
return (Token){TOKEN_IDENTIFIER, current_token.value};
} else if (isdigit(current_token.value[0])) {
int i = 0;
while (isdigit(current_token.value[i] = getchar())) {
i++;
}
current_token.value[i] = '\0';
return (Token){TOKEN_INTEGER, current_token.value};
} else {
switch (current_token.value[0]) {
case '+': return (Token){TOKEN_PLUS, "+"};
case '-': return (Token){TOKEN_MINUS, "-"};
case '*': return (Token){TOKEN_MULTIPLY, "*"};
case '/': return (Token){TOKEN_DIVIDE, "/"};
case '=': return (Token){TOKEN_ASSIGNMENT, "="};
case ';': return (Token){TOKEN_SEMICOLON, ";"};
default: error("Unknown token");
}
}
}
语法分析器的任务是解析标记流并构建语法树。我们可以使用递归下降解析器来实现这一功能。以下是一个简单的语法分析器示例,支持表达式的解析:
typedef struct {
char name[100];
int value;
} Variable;
Variable variables[100];
int variable_count = 0;
int find_variable_index(const char *name) {
for (int i = 0; i < variable_count; i++) {
if (strcmp(variables[i].name, name) == 0) {
return i;
}
}
return -1;
}
void declare_variable(const char *name) {
int index = find_variable_index(name);
if (index == -1) {
strcpy(variables[variable_count].name, name);
variables[variable_count].value = 0;
variable_count++;
} else {
error("Variable already declared");
}
}
int parse_expression() {
int result = parse_term();
while (1) {
Token t = get_next_token();
if (t.type == TOKEN_PLUS) {
result += parse_term();
} else if (t.type == TOKEN_MINUS) {
result -= parse_term();
} else {
unget_token(t);
break;
}
}
return result;
}
int parse_term() {
int result = parse_factor();
while (1) {
Token t = get_next_token();
if (t.type == TOKEN_MULTIPLY) {
result *= parse_factor();
} else if (t.type == TOKEN_DIVIDE) {
result /= parse_factor();
} else {
unget_token(t);
break;
}
}
return result;
}
int parse_factor() {
Token t = get_next_token();
if (t.type == TOKEN_INTEGER) {
return atoi(t.value);
} else if (t.type == TOKEN_IDENTIFIER) {
int index = find_variable_index(t.value);
if (index == -1) {
error("Undefined variable");
}
return variables[index].value;
} else {
error("Expected integer or identifier");
}
}
void parse_statement() {
Token t = get_next_token();
if (t.type == TOKEN_IDENTIFIER) {
declare_variable(t.value);
t = get_next_token();
if (t.type != TOKEN_ASSIGNMENT) {
error("Expected '='");
}
int value = parse_expression();
int index = find_variable_index(t.value);
variables[index].value = value;
t = get_next_token();
if (t.type != TOKEN_SEMICOLON) {
error("Expected ';'");
}
} else {
error("Expected identifier");
}
}
在语义分析阶段,我们需要确保变量声明正确且没有重复定义。同时,我们可以生成伪汇编代码作为输出。例如,对于输入 a = 3 + 4;
,我们可以生成如下伪汇编代码:
LOAD_CONST 3
LOAD_CONST 4
ADD
STORE_VAR a
以下是生成伪汇编代码的示例函数:
void generate_code(int value, const char *var_name) {
printf("LOAD_CONST %d\n", value);
printf("STORE_VAR %s\n", var_name);
}
通过上述步骤,我们实现了一个非常基础的编译器。尽管这个编译器的功能有限,但它展示了编译器的核心工作流程:从词法分析到语法分析,再到语义分析和代码生成。