Escribimos una máquina virtual de pila en Rust'e

Hola Habr! Desde hace varias semanas, he estado desarrollando mi lenguaje de programación Rust. Me gustaría hablar sobre lo que un novato en este negocio puede enfrentar y lo que debe saber.


Breve historia


Todo comenzó con una bifurcación de ein , lo bifurqué para aprender cómo se construyen los lenguajes de programación. Como ein se interpreta desde y hacia, su velocidad de ejecución no fue la más alta, y después de que comencé a entender algo, decidí comenzar a escribir mi propio intérprete, que al final también abandonó.


¡Pero es demasiado temprano para desesperarse! Leí un par de artículos sobre VM y lo que son y decidí escribir una VM apilada simple.


¿Qué es una "máquina virtual apilada" y cómo funciona?


En habr hay un artículo otdelny al respecto, pero que para no conducir en enlaces explicaré brevemente el significado de esta pequeña cosa.


La pila VM realiza todas las operaciones en los datos que se almacenan en forma de pila, cada operación recupera la cantidad necesaria de datos para la operación y después de la ejecución puede "enviar" un nuevo número a la pila.


Empezando


Primero necesitas crear un nuevo proyecto usando carga:


cargo new habr_vm 

Primero, necesitamos crear algunas operaciones básicas para nuestra VM:


 enum Opcode { Push(i32), Add, AddAssign(i32), Sub, SubAssign(i32), } 

Estas son nuestras operaciones básicas, el comando Push agregará un nuevo número a la pila, Add y Sub tomarán dos números de la pila y realizarán acciones con ellos (suma y resta, respectivamente), no necesito explicar AddAssign y SubAssign.


La siguiente tarea es crear la máquina virtual en sí, para esto crearemos una estructura no complicada:


 struct Vm { pub stack: Vec<i32>, } 

Y lo implementamos:


 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); } } } } } 

Implementamos nuestra estructura, ¿qué sigue? Luego necesitamos crear nuestro "programa".


Así es como debería verse:


 let program = vec![ Opcode::Push(2),// 2    Opcode::Push(4),//  4    Opcode::Sub,//  4 - 2 ]; 

Es simple, ¿no es así? Si es así, ¡ejecutemos nuestro programa!


 let mut vm = Vm {stack: Vec::new()}; vm.run(program); //     ,       2 for i in vm.stack() { println!("{}", i); } //  2 

Es muy simple para mí, por lo que puede agregar suficientes códigos de operación para la operación que necesita.


Conclusión


Creo que he explicado claramente cómo escribir todo esto en un rast y cómo funciona.


Me gustaría agregar que puede escribir fácilmente su propio YP gracias a una VM similar, básicamente solo tiene que escribir un analizador, lexer y "compilador", y si desea ver un proyecto terminado, puede seguir este enlace .


Todo el código del artículo está disponible en este repositorio.


Buena suerte Habr!

Source: https://habr.com/ru/post/es416505/


All Articles