Escrevemos uma máquina virtual de pilha no Rust'e

Olá Habr! Há várias semanas, desenvolvo minha linguagem de programação Rust. Gostaria de falar sobre o que um novato nesse ramo pode enfrentar e o que ele deve saber.


Breve Histórico


Tudo começou com um fork do ein , bifurquei-o para aprender como as linguagens de programação são construídas. Como ein é interpretado de e para, sua velocidade de execução não era a mais alta e, depois que comecei a entender alguma coisa, decidi começar a escrever meu próprio intérprete, que no final também abandonou.


Mas é muito cedo para se desesperar! Li alguns artigos sobre VM e o que são e decidi escrever uma VM empilhada simples.


O que é uma "máquina virtual empilhada" e como funciona?


No habr, há um artigo otdelny sobre isso, mas, para não usar links, explicarei brevemente o significado dessa coisinha.


A VM da pilha executa todas as operações nos dados armazenados na forma de uma pilha, cada operação recupera a quantidade necessária de dados para a operação e, após a execução, pode "enviar" um novo número para a pilha.


Introdução


Primeiro, você precisa criar um novo projeto usando carga:


cargo new habr_vm 

Primeiro, precisamos criar algumas operações básicas para nossa VM:


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

Essas são nossas operações básicas, o comando Push adicionará um novo número à pilha, Add e Sub pegarão dois números da pilha e executarão ações com eles (adição e subtração, respectivamente), não preciso explicar o AddAssign e o SubAssign.


A próxima tarefa é criar a própria máquina virtual, para isso criaremos uma estrutura não complicada:


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

E nós o 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 nossa estrutura, o que vem depois? Em seguida, precisamos criar nosso "programa".


Veja como deve ficar:


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

É simples, não é? Se sim, então vamos executar o nosso programa!


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

É muito simples quanto a mim, então você pode adicionar códigos de operação suficientes para a operação que você precisa.


Conclusão


Eu acho que expliquei claramente como escrever tudo isso de uma maneira geral e como funciona.


Gostaria de acrescentar que você pode escrever facilmente seu próprio YP graças a uma VM semelhante, basicamente só precisa escrever um analisador, um lexer e um "compilador" e, se quiser ver um projeto concluído, pode seguir este link .


Todo o código do artigo está disponível neste repositório.


Boa sorte, Habr!

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


All Articles