- PVSM.RU - https://www.pvsm.ru -
Здравствуй! Уже несколько недель я занят разработкой своего языка программирования на Rust. Мне бы хотелось расказать о том с чем может столкнуться новичок в этом деле и о чем ему следует знать.
Все началось с форка ein [1], форкнул я его дабы изучить как строятся языки программирования. Так-как ein интерпретируется от и до, то его скорость исполнения оказалась не самой высокой и после того как я начал хоть что то понимать я решил начать писать свой интерпретатор который в конце концов тоже забросил.
Но отчаиваться еще рано! Я прочел пару статей о VM и о том какие они бывают и решил написать простую стековую VM.
На хабре есть отедльная статья [2] про это, но что бы не гонять по ссылкам изложу вкратце смысл этой вещицы.
Стековая VM выполняет все операции над данными, которые хранятся в виде стека, каждая операция извлекает нужное количество ей данных для операции и после выполнения ее может "отправить" в стек новое число.
Для начала необходимо создать новый проект с помощью cargo:
cargo new habr_vm
Сперва нам необходимо создать несколько базовых операций для нашей VM:
enum Opcode {
Push(i32),
Add,
AddAssign(i32),
Sub,
SubAssign(i32),
}
Это наши базовые операции, команда Push будет добавлять в стек новое число, Add и Sub будут брать из стека два числа и выполнять с ними действия(сложение и вычитание соответственно), думаю о AddAssign и SubAssign объяснять не надо.
Cледующая задача это создать саму виртуальную машину, для этого мы создадим не сложную структуру:
struct Vm {
pub stack: Vec<i32>,
}
И имплементируем ее:
impl Vm {
//Упрощяем себе жизнь тем что сокращаем писанину
pub fn pop(&mut self) -> i32 {
self.stack.pop().unwrap()
}
//Здесь будет происходить все самое интересное
pub fn run(&mut self,program: Vec<Opcode>) {
for opcode in program { //Проверяем каждую команду в нашей программе
match opcode {
Opcode::Push(n) => {
//Просто добавляем число в наш стек
self.stack.push(n);
}
Opcode::Add => {
// Берем два числа из стека и складываем их, добавляем в стек уже новое число
let value = self.pop() + self.pop();
self.stack.push(value);
}
Opcode::Sub => {
// Тут то же самое что и с складыванием только наоборот
let value = self.pop() - self.pop();
self.stack.push(value);
}
// Думаю следующие две операции объяснять не нужно
Opcode::AddAssign(n) => {
let mut value = self.pop();
value += n;
self.stack.push(value);
}
Opcode::SubAssign(n) => {
let mut value = self.pop();
value += n;
self.stack.push(value);
}
}
}
}
}
Мы иплементировали нашу структуру, что дальше? Дальше надо создать нашу "программу".
Вот как это должно выглядеть:
let program = vec![
Opcode::Push(2),//Добавляем 2 в наш стек
Opcode::Push(4),// Добавляем 3 в наш стек
Opcode::Sub,// Вычитаем 4 - 2
];
Все просто, не так ли? Раз так, то давайте уже запустим нашу программу!
let mut vm = Vm {stack: Vec::new()};
vm.run(program);
// Выводим все числа из стека, на данный момент это будет просто 2
for i in vm.stack() {
println!("{}", i);
}
// Вывод
2
Очень просто как по мне, таким образом вы можете добавить еще достаточно опкодов для нужной вам операции.
Думаю что я вполне понятно объяснил как это все написать на расте и как это работает.
Хочется добавить что вы с легкостью сможете написать свой ЯП благодаря подобному VM, вам по сути остается написать только парсер, лексер и "компилятор", а если вам охото посмотреть на уже готовый проект то можете пройти по данной ссылке [3].
Весь код из статьи доступен в данном репозитории [4]
Удачи Хабр!
Автор: aprokurov
Источник [5]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/rust/285421
Ссылки в тексте:
[1] ein: https://github.com/17cupsofcoffee/ein
[2] статья: https://habr.com/post/138667/
[3] ссылке: https://gitlab.com/playX/Neo
[4] репозитории: https://gitlab.com/playX/habr_vm/blob/master/LICENSE
[5] Источник: https://habr.com/post/416505/?utm_source=habrahabr&utm_medium=rss&utm_campaign=416505
Нажмите здесь для печати.