[Advance C Programming] Как написать свой собственный компилятор

  1. Выберите язык:

Определите язык высокого уровня, который вы хотите скомпилировать. Это определит синтаксис и особенности языка, с которым вы будете работать.
Для этого примера выберем простой вымышленный язык под названием «Простой». Синтаксис Simple похож на C и имеет несколько основных конструкций, таких как переменные, операторы if-else и циклы.

  1. Напишите код лексера:

Первым шагом в написании компилятора является написание лексера, который представляет собой программу, которая разбивает исходный код на поток токенов, таких как ключевые слова, символы и идентификаторы.
Вот пример лексера для Simple:

#include <stdio.h>
#include <string.h>
#include <ctype.h>
enum TokenType {
 T_INT,
 T_FLOAT,
 T_PLUS,
 T_MINUS,
 T_MUL,
 T_DIV,
 T_LPAREN,
 T_RPAREN,
 T_ID,
 T_IF,
 T_ELSE,
 T_WHILE,
 T_EOL
};
struct Token {
 enum TokenType type;
 char value[100];
};
int is_keyword(char *value)
{
 if (strcmp(value, "if") == 0) {
 return T_IF;
 } else if (strcmp(value, "else") == 0) {
 return T_ELSE;
 } else if (strcmp(value, "while") == 0) {
 return T_WHILE;
 }
 return T_ID;
}
int get_token(struct Token *token)
{
 int i = 0;
 char c;
while (isspace(c = getchar()))
 ;
if (isdigit(c)) {
 while (isdigit(c)) {
 token->value[i++] = c;
 c = getchar();
 }
 ungetc(c, stdin);
 token->value[i] = '\0';
 token->type = T_INT;
 } else if (c == '+') {
 token->value[0] = '+';
 token->value[1] = '\0';
 token->type = T_PLUS;
 } else if (c == '-') {
 token->value[0] = '-';
 token->value[1] = '\0';
 token->type = T_MINUS;
 } else if (c == '*') {
 token->value[0] = '*';
 token->value[1] = '\0';
 token->type = T_MUL;
 } else if (c == '/') {
 token->value[0] = '/';
 token->value[1] = '\0';
 token->type = T_DIV;
 } else if (c == '(') {
 token->value[0] = '(';
 token->value[1] = '\0';
 token->type = T_LPAREN;
 } else if (c == ')') {
 token->value[0] = ')';
 token->value[1] = '\0';
 token->type = T_RPAREN;
 } 
Note : this will turn into a long code so only a short snippet is given
  1. Написать код парсера:

Следующим шагом является написание синтаксического анализатора, который берет поток токенов из лексера и создает синтаксическое дерево, представляющее структуру программы.
Вот пример парсера для Simple:

#include <stdio.h>
#include <string.h>
struct Token {
 enum TokenType type;
 char value[100];
};
struct Node {
 int type;
 struct Node *left;
 struct Node *right;
 int value;
};
struct Node *expression();
struct Node *term();
struct Node *factor();
int get_token(struct Token *token);
struct Node *expression()
{
 struct Node *node = term();
struct Token token;
 while (get_token(&token) == T_PLUS || get_token(&token) == T_MINUS) {
 struct Node *new_node = (struct Node *)malloc(sizeof(struct Node));
 new_node->type = get_token(&token);
 new_node->left = node;
 new_node->right = term();
 node = new_node;
 }
return node;
}
struct Node *term()
{
 struct Node *node = factor();
struct Token token;
 while (get_token(&token) == T_MUL || get_token(&token) == T_DIV) {
 struct Node *new_node = (struct Node *)malloc(sizeof(struct Node));
 new_node->type = get_token(&token);
 new_node->left = node;
 new_node->right = factor();
 node = new_node;
 }
return node;
}
struct Node *factor()
{
 struct Token token;
 get_token(&token);
if (token.type == T_INT) {
 struct Node *node = (struct Node *)malloc(sizeof(struct Node));
 node->type = T_INT;
 node->value = atoi(token.value);
 return node;
 } else if (token.type == T_LPAREN) {
 struct Node *node = expression();
 get_token(&token);
 return node;
 }
 return 0;
}

  1. Генерация кодов: Генератор кода берет синтаксическое дерево из синтаксического анализатора и генерирует машинный код, реализующий программу.
    генерация кода для Simple:
void generate_code(struct Node *node)
{
 if (node->type == T_INT) {
 printf(" push %d\n", node->value);
 } else if (node->type == T_PLUS) {
 generate_code(node->left);
 generate_code(node->right);
 printf(" pop rbx\n");
 printf(" pop rax\n");
 printf(" add rax, rbx\n");
 printf(" push rax\n");
 } else if (node->type == T_MINUS) {
 generate_code(node->left);
 generate_code(node->right);
 printf(" pop rbx\n");
 printf(" pop rax\n");
 printf(" sub rax, rbx\n");
 printf(" push rax\n");
 } 
 
  1. Объединить все :
    Вам потребуется интегрировать лексер, синтаксический анализатор и генератор кода в единую программу, которая считывает исходный код, генерирует машинный код и выводит исполняемый файл.

Если вы только начинаете, вы можете начать с небольшого проекта, который фокусируется на определенном аспекте конструкции компилятора, таком как лексер или синтаксический анализатор, прежде чем пытаться написать полный компилятор.

Или, если вы только начинаете изучать какие-либо языки компьютерного программирования, следуйте основной серии C в моих предыдущих постах.

Похожие записи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *