Wir schreiben eine virtuelle Stapelmaschine auf Rust'e

Hallo Habr! Seit einigen Wochen entwickle ich meine Programmiersprache Rust. Ich möchte darüber sprechen, was ein Neuling in diesem Geschäft erwartet und worüber er Bescheid wissen sollte.


Kurze Geschichte


Alles begann mit einer Gabelung von ein , ich gabelte sie, um zu lernen, wie Programmiersprachen aufgebaut sind. Da ein von und nach interpretiert wird, war seine Ausführungsgeschwindigkeit nicht die höchste, und nachdem ich anfing, etwas zu verstehen, beschloss ich, meinen eigenen Dolmetscher zu schreiben, der am Ende auch aufgab.


Aber es ist zu früh, um zu verzweifeln! Ich las ein paar Artikel über VM und was sie sind und beschloss, eine einfache gestapelte VM zu schreiben.


Was ist eine "gestapelte virtuelle Maschine" und wie funktioniert sie?


Auf habr gibt es einen otdelny Artikel darüber, aber um nicht auf Links zu fahren, werde ich kurz die Bedeutung dieser kleinen Sache erklären.


Die Stapel-VM führt alle Operationen an Daten aus, die in Form eines Stapels gespeichert sind. Jede Operation ruft die für die Operation erforderliche Datenmenge ab und kann nach der Ausführung eine neue Nummer an den Stapel "senden".


Erste Schritte


Zuerst müssen Sie ein neues Projekt mit Fracht erstellen:


cargo new habr_vm 

Zunächst müssen wir einige grundlegende Vorgänge für unsere VM erstellen:


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

Dies sind unsere grundlegenden Operationen. Der Push-Befehl fügt dem Stapel eine neue Nummer hinzu. Add und Sub nehmen zwei Nummern aus dem Stapel und führen Aktionen mit ihnen aus (Addition bzw. Subtraktion). AddAssign und SubAssign müssen nicht erläutert werden.


Die nächste Aufgabe besteht darin, die virtuelle Maschine selbst zu erstellen. Dazu erstellen wir eine nicht komplizierte Struktur:


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

Und wir setzen es um:


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

Wir haben unsere Struktur implementiert, wie geht es weiter? Als nächstes müssen wir unser "Programm" erstellen.


So sollte es aussehen:


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

Es ist einfach, nicht wahr? Wenn ja, dann lassen Sie uns unser Programm ausführen!


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

Für mich ist das sehr einfach, sodass Sie genügend Opcodes für den gewünschten Vorgang hinzufügen können.


Fazit


Ich denke, ich habe ganz klar erklärt, wie man das alles auf einen Rast schreibt und wie es funktioniert.


Ich möchte hinzufügen, dass Sie dank einer ähnlichen VM problemlos Ihre eigene YP schreiben können. Sie müssen im Grunde nur einen Parser, einen Lexer und einen "Compiler" schreiben. Wenn Sie sich ein fertiges Projekt ansehen möchten, können Sie diesem Link folgen.


Der gesamte Code aus dem Artikel ist in diesem Repository verfügbar .


Viel Glück Habr!

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


All Articles