Пишем упрощенный ассемблер и виртуальный процессор. Часть 1

от автора


Последнее время я очень интересовался компиляторами, низкоуровневыми языками программирования и архитектурой компьютера. Однако на хабре про компиляторы низкоуровневых языков, нашел очень мало информации. Поэтому я решил рассказать о ассемблере и написать свой. Весь код в статье написан на языке Си из-за быстроты программ, написанных на нем, и его простоты.

Набор команд и грамматика
Набор команд не должен быть большим. У меня получилось всего 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/


Комментарии

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

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