Последнее время я очень интересовался компиляторами, низкоуровневыми языками программирования и архитектурой компьютера. Однако на хабре про компиляторы низкоуровневых языков, нашел очень мало информации. Поэтому я решил рассказать о ассемблере и написать свой. Весь код в статье написан на языке Си из-за быстроты программ, написанных на нем, и его простоты.
Набор команд и грамматика
Набор команд не должен быть большим. У меня получилось всего 18 команд.
Теперь, давайте определимся что наш виртуальный процессор должен уметь делать.
Прежде всего, это пересылка данных, работа со стеком и базовая арифметика. Но как же процессор будет давать/получать данные о своей работе?
Для это нам нужно будет сделать ввод-вывод и возвращение результата выполнения. Все. Никаких функций, переменных и переходов. Только хардкор. Однако язык будет условным, как ARM condition language. Раз он условный, нужно вырабатывать условия, пусть это будут делать сравнения.
Теперь простейший пример программы на языке:
al mov r00, #0 al cmp r00, #400 av return #0 le mul r01, r00, r00 le swi #5 le inc r00 le mov r14, #1
Разбор кода:
al mov r00, #0 — в любом случае r00 = 0
al cmp r00, #400 — в любом случае сравним r00 и 400
av return 0 — если r00 больше, завершаем выполнение с кодом 0
le mul r01, r00, r00 — если r00 меньше или равно, r01 = r00 * r00
le swi #5 — если r00 меньше или равно, закрашиваем пиксель(r00, r01)
le inc r00 — если r00 меньше или равно, увеличим r00 на 1.
le mov r14, #1 — если r00 меньше или равно, счетчик программы = 1
Для начала напишем парсер, создающий из входной строки объектное(общее) представление кода. Самый простой способ это сделать — сохранить условие, команду и аргументы в структуру.
Заголовочный файл:
#ifndef PARSER_H #define PARSER_H struct operation { char cond[32]; // Условие выполнения char cmd[32]; // Сама команда char arg0[32]; // Первый аргумент char arg1[32]; // Второй аргумент char arg2[32]; // Третий агрумент }; void dump_operation(struct operation *op); // Вывод данных операции struct operation *parse_code(const char *code); // Парсит строку в общее представление кода #endif // PARSER_H
Исходный файл:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <ctype.h> #include "parser.h" #define SON(x) (((x) && (*(x) != '\0'))?(x):"NONE") //Если строка пуста или указатель нулевой, то выдаем NONE (для dump_operation) void dump_operation(struct operation *op) { if (!op) { // Если дан неверный указатель printf("OP: NULL\n"); return; } // Если указатель в порядке printf("COND: \"%s\"\nOP: \"%s\"\nARGS: \"%s\", \"%s\", \"%s\"\n\n", SON(op->cond), SON(op->cmd), SON(op->arg0), SON(op->arg1), SON(op->arg2)); } static inline const char *skip(const char *line, char *skips) // Пропуск строки в строке { line += strlen(skips); // Увеличим указатель на пропарсиваемую строку на длину пропускаемой while ((*line == ' ') || (*line == ',')) // Пропустим ненужные символы line++; if ((*line == '\0') || (*line == '\n') || (*line == ';')) // Если конец строки return 0; // Вернем нулевой указатель return line; // Иначе вернем полученный указатель } static inline void str_to_lower(char *str) // Переводит строку в нижний регистр { unsigned int l = strlen(str); unsigned int i = 0; for (i = 0; i < l; i++) str[i] = (char)tolower(str[i]); } struct operation *parse_code(const char *code) { struct operation *op = (struct operation*)calloc(1, sizeof(*op)); // Выделим память под результат char *tokens[5] = {op->cond, op->cmd, op->arg0, op->arg1, op->arg2}; unsigned int i = 0; for (i = 0; i < 5; i++) { if (sscanf(code, "%[0-9a-zA-Z@#$]", tokens[i]) <= 0/*Получаем строку в текущий токен*/) { // Если встретили запрещенный символ fprintf(stderr, "Error!\nUnknown symbol!\n"); // Говорим об этом free((void *)op); // Освобождаем память return 0; // Возвращаем нулевой указатель } str_to_lower(tokens[i]); // Переводим строку в нижний регистр code = skip(code, tokens[i]); // Пропускаем полученный токен if (!code) // Если код закончился break; // Завершаем цикл } return op; // Возвращаем объектное представление кода }
Разбор кода не пишу, все написано в комментариях.
В следующей части расскажу об анализе кода и переводе его в байт-код.
Литература по теме
Э. Таненбаум Т.Остин Архитектура компьютера
А. Ахо, Р. Сети, Д. Ульман Компиляторы: принципы, технологии и инструменты
ссылка на оригинал статьи http://habrahabr.ru/post/272891/
Добавить комментарий