Compilerbau leicht gemacht
1. Die Einleitung
2. Was wird benötigt
3. Die Theorie
4. Der Code
5. Abschließende Worte
1. Die Einleitung
Ich heiße dich herzlich willkommen zu diesem Tutorial, in dem ich zeigen möchte dass es mit Hilfe von VB.Net sehr einfach ist einen eigenen Compiler zu bauen. Aber zunächst sollte man sich fragen: Für was brauche ich einen eigenen Compiler? Die Antwort ist aber schnell gefunden. Man kann einen eigenen Compiler dazu benutzen eine eigene Script- bzw. Programmiersprache zu kreieren. Diese kann man dann beispielsweise im eigenen Programm verwenden um damit Addons für das eigene Programm zu schreiben oder man versuchst es einfach mal aus Spaß.
So und bevor wir hier weiter um den heißen Brei reden, würde ich sagen legen wir los, oder?
2. Was wird benötigt
Um genau zu sein braucht man nur eine IDE (z.B. Visual Basic .Net Express) und sein Hirn. Siehst du? Fängt doch schon einfach an!
3. Die Theorie
Ich weiß Theorie ist nicht immer schön, aber man soll ja auch das Prinzip verstehen und nicht einfach nur Abtippen.
Hier möchte ich ein wenig auf die Funktionsweise eines Compilers eingehen. Zuerst einmal, was macht ein Compiler? Ein Compiler übersetzt den eingegebenen Code in eine andere Programmiersprache oder direkt in Maschinencode. Um den Code zu übersetzen teilt er diesen in sog. Token auf und verarbeitet diese dann einzeln weiter.
Das kann man so verstehen:
Der eingegebene Code:
Dieser Code wird nun in Token zerkleinert:
Token1 = Do Something
Token2 = Do Something else
Die Token werden in sich noch einmal mit dem selben Prinzip verarbeite. Tja mehr gibt’s hier eigentlich nicht zu sagen, ohne Leute die nicht mit der Materie vertraut sind zu verwirren. Und ehrlich gesagt ist das für uns hier auch nicht weiter von Bedeutung. Also weiter geht’s.
4. Der Code
Nun kommt der Interessante Teil. Wie wollen wir die Token Zehrlegung und die eigentliche Compelierungsarbeit umsetzen? Zu aller erst legt ihr eine neue Klasse an, Name ist egal.
So nun brauchen wir ein paar Imports:
System.Text brauchen wir für den StringBuilder und System.CodeDom.Compiler ist ein dienst von Visual Basic mit dem man VB Code Compelieren kann, dass heißt der größte Teil wurde uns von Microsoft schon abgenommen
Jetzt sollten wir und mal überlegen wie unsere Klasse aufgebaut sein muss damit wir unser Ziel erreichen. Dabei kann man auf folgende Struktur kommen;
Zu Erklärung:
start() – Mit dieser Sub Routine startet man den Compiler
parse() – Hier zerlegen wir den Code in die Token
CompileToken() – Erklärt sich wohl selbst
compile() – Hier schreiben wir den Fertigen Code in eine EXE
Fangen wir dann mal mit start() an. Zuerst müssen wir uns überlegen welche Parameter wir brauchen. Zur Zeit brauchen wir nur den Code und den Namen des Programmes. Ansonsten rufen wir in start() nur die unterschiedlichen Routinen auf und geben zum Schluss noch einen Rückgabewert aus.
Das sollte nun so aussehen:
Die Variable endCode soll später den fertig Compelietren Code enthalten und wird dazu als StringBuilder festgelegt, weil dieser sich für so etwas eher anbietet als ein normaler String. Linenr gibt an in welcher Codezeile wir uns befindet, was uns später für die Fehler ausgabe zugute kommt.
parse() ist nicht ganz so simpel. Zunächst mal müssen wir den Code in Token aufteilen.
Hier überprüfen wir auch gleich ob der Code überhaupt etwas beinhaltet.
Nachdem man die Token erstellt hat muss man sie zunächst einmal „zuschneiden“ und kann man wie folgt machen:
Wir haben mit Visual Basic den Vorteil, dass Microsoft schon eine Artk kleinen Parser für uns Anbietet, den wir auch nutzen werden.
Damit wir aber mit dem TextFieldParser so richtig durchstarten können, brauchen wir noch ein paar Variablen.
Die TokenFields werden später unsere nochmals unterteilten Token sein.
So nun brauchen wir eine Schleife in der wir die einzelnen Token auslesen und sie weiter verarbeiten. Hier hilft uns der Befehel SourceParser.EndofData weiter:
Das wars dann auch schon mit parse(), aber sicher ist euch aufgefallen das wir in der Schleife CompileToken() aufrufen um den Token zu Compelieren und gegebenen Falls die weitere Compelierung stoppen.
Bevor wir uns an CompileToken() ran wagen, sollten wir uns erst mal überlegen, welche Befehle unsere Sprache bekommen soll. In diesem Tutorial beschränke ich mich einfach mal auf die Nötigsten:
PRINT – Gibt einen Text aus
GET – Ließt eine Eingabe aus
WAIT – Wartet auf eine Tasteneingabe
Puh! Ganz schön viel Code auf einmal, aber schnell erklärt. Wir überprüfen hier einfach den Token der uns übergeben wird und übersetzen ihn in VB Code. Im falle von PRINT brauchen wir noch eine schleife, denn wenn wir diese weglassen würden, würde bei „PRINT Hallo Welt“ nur das Hallo ausgegeben werden. Auch hab ich gleich eine überprüfung eingebaut ob sich im PRINT Befehl eine Variable befindet. Eine Variable wird mit $ erkannt. Ein Beispiel:
Damit können wir jetzt Theoretisch schon Programmieren.
Der WAIT Befehl sollte zum Schluss immer da stehen, sonst schließt sich die Konsole später zu schnell und man kann ihren Inhalt nicht lesen.
Die Funktion compile() geb ich euch jetzt einfach vor, denn es würde zu lange dauern euch den VBCodeProvider genau zu erklären. Dafür gibt’s schon genug Tutorials.
Also hier die Letzte Funktion:
In dieser Funktion wird euer bisheriger Code in eine Standard Konsolen Anwendung geschrieben und diese dann zu einem Programm Compeliert. Das fertig Compelierte Programm sollte dann im selben Ordner erscheinen wie euer Compiler (Bei einem Testdurchlauf wahrscheinlich in DEBUG Ordner).
Zum Schluss noch einmal die gesamte Klasse:
5. Abschließende Worte
Ich hoffe ich konnte euch einen guten Einblick in den Aufbau eines Compiler geben, muss aber sagen das wir hier nur den Simpelsten Compiler überhaupt gebaut haben, also nicht gleich überall damit Brüsten ;). Dann wünsche ich euch noch viel Spaß mit euren eigenen Compiler.
Zum Schluss noch:
Dieses Tutorial darf nicht ohne Erlaubnis des Autors verbreitet werden
mit Visual Basic.Net
Inhalt1. Die Einleitung
2. Was wird benötigt
3. Die Theorie
4. Der Code
5. Abschließende Worte
1. Die Einleitung
Ich heiße dich herzlich willkommen zu diesem Tutorial, in dem ich zeigen möchte dass es mit Hilfe von VB.Net sehr einfach ist einen eigenen Compiler zu bauen. Aber zunächst sollte man sich fragen: Für was brauche ich einen eigenen Compiler? Die Antwort ist aber schnell gefunden. Man kann einen eigenen Compiler dazu benutzen eine eigene Script- bzw. Programmiersprache zu kreieren. Diese kann man dann beispielsweise im eigenen Programm verwenden um damit Addons für das eigene Programm zu schreiben oder man versuchst es einfach mal aus Spaß.
So und bevor wir hier weiter um den heißen Brei reden, würde ich sagen legen wir los, oder?
2. Was wird benötigt
Um genau zu sein braucht man nur eine IDE (z.B. Visual Basic .Net Express) und sein Hirn. Siehst du? Fängt doch schon einfach an!
3. Die Theorie
Ich weiß Theorie ist nicht immer schön, aber man soll ja auch das Prinzip verstehen und nicht einfach nur Abtippen.
Hier möchte ich ein wenig auf die Funktionsweise eines Compilers eingehen. Zuerst einmal, was macht ein Compiler? Ein Compiler übersetzt den eingegebenen Code in eine andere Programmiersprache oder direkt in Maschinencode. Um den Code zu übersetzen teilt er diesen in sog. Token auf und verarbeitet diese dann einzeln weiter.
Das kann man so verstehen:
Der eingegebene Code:
Dieser Code wird nun in Token zerkleinert:
Token1 = Do Something
Token2 = Do Something else
Die Token werden in sich noch einmal mit dem selben Prinzip verarbeite. Tja mehr gibt’s hier eigentlich nicht zu sagen, ohne Leute die nicht mit der Materie vertraut sind zu verwirren. Und ehrlich gesagt ist das für uns hier auch nicht weiter von Bedeutung. Also weiter geht’s.
4. Der Code
Nun kommt der Interessante Teil. Wie wollen wir die Token Zehrlegung und die eigentliche Compelierungsarbeit umsetzen? Zu aller erst legt ihr eine neue Klasse an, Name ist egal.
So nun brauchen wir ein paar Imports:
System.Text brauchen wir für den StringBuilder und System.CodeDom.Compiler ist ein dienst von Visual Basic mit dem man VB Code Compelieren kann, dass heißt der größte Teil wurde uns von Microsoft schon abgenommen
Jetzt sollten wir und mal überlegen wie unsere Klasse aufgebaut sein muss damit wir unser Ziel erreichen. Dabei kann man auf folgende Struktur kommen;
Zu Erklärung:
start() – Mit dieser Sub Routine startet man den Compiler
parse() – Hier zerlegen wir den Code in die Token
CompileToken() – Erklärt sich wohl selbst
compile() – Hier schreiben wir den Fertigen Code in eine EXE
Fangen wir dann mal mit start() an. Zuerst müssen wir uns überlegen welche Parameter wir brauchen. Zur Zeit brauchen wir nur den Code und den Namen des Programmes. Ansonsten rufen wir in start() nur die unterschiedlichen Routinen auf und geben zum Schluss noch einen Rückgabewert aus.
Das sollte nun so aussehen:
VB.NET-Quellcode
- Public Class Compiler
- Private linenr As Integer = 0
- Private endCode As StringBuilder
- Private ExeName As String = ""
- Public Function start(ByVal Code As String, Optional ByVal name As String = "App") As String
- Me.endCode = New StringBuilder("")
- Me.ExeName = name parse(Code.Split(vbCrLf).ToList)
- Return compile()
- End Function
- Private Function parse()
- End Function
- Private Function CompileToken()
- End Function
- Private Function compile()
- End Function
- End Class
Die Variable endCode soll später den fertig Compelietren Code enthalten und wird dazu als StringBuilder festgelegt, weil dieser sich für so etwas eher anbietet als ein normaler String. Linenr gibt an in welcher Codezeile wir uns befindet, was uns später für die Fehler ausgabe zugute kommt.
parse() ist nicht ganz so simpel. Zunächst mal müssen wir den Code in Token aufteilen.
Hier überprüfen wir auch gleich ob der Code überhaupt etwas beinhaltet.
Nachdem man die Token erstellt hat muss man sie zunächst einmal „zuschneiden“ und kann man wie folgt machen:
Wir haben mit Visual Basic den Vorteil, dass Microsoft schon eine Artk kleinen Parser für uns Anbietet, den wir auch nutzen werden.
Damit wir aber mit dem TextFieldParser so richtig durchstarten können, brauchen wir noch ein paar Variablen.
Die TokenFields werden später unsere nochmals unterteilten Token sein.
So nun brauchen wir eine Schleife in der wir die einzelnen Token auslesen und sie weiter verarbeiten. Hier hilft uns der Befehel SourceParser.EndofData weiter:
VB.NET-Quellcode
- While Not ScriptParser.EndOfData
- linenr = SourceParser.LineNumber
- Try
- TokenFields = SourceParser.ReadFields 'Versuchen den nächsten Token zu lesen
- Catch ex As Exception 'Wenn ein Fehler auftritt
- MsgBox("Error Message: " & ex.Message & vbNewLine, MsgBoxStyle.Critical, "Error") 'Fehler ausgeben
- TokenStream.Close() 'Tokenstream Schließen
- TokenStream = Nothing
- Exit While
- End Try
- If CompileToken(TokenFields) = False Then Return False 'Bei falschen Token, Compelierung beenden
- End While
- Return True
Das wars dann auch schon mit parse(), aber sicher ist euch aufgefallen das wir in der Schleife CompileToken() aufrufen um den Token zu Compelieren und gegebenen Falls die weitere Compelierung stoppen.
Bevor wir uns an CompileToken() ran wagen, sollten wir uns erst mal überlegen, welche Befehle unsere Sprache bekommen soll. In diesem Tutorial beschränke ich mich einfach mal auf die Nötigsten:
PRINT – Gibt einen Text aus
GET – Ließt eine Eingabe aus
WAIT – Wartet auf eine Tasteneingabe
VB.NET-Quellcode
- Private Function CompileToken(ByVal token() As String)
- Select Case token(0)
- Case "PRINT"
- Dim buf As String
- For i As Integer = 1 To token.Length - 1
- If (token(i).Substring(0, 1) = "$") Then
- buf += Chr(34) & " & " & token(i).Substring(1) & " & " & Chr(34)
- Else
- buf += token(i) & " "
- End If
- Next
- Me.endCode.Append("Console.WriteLine(" & Chr(34) & buf & Chr(34) & ")")
- Case "GET"
- Me.endCode.Append("Dim " & token(1) & " as String = Console.ReadLine()")
- Case "WAIT"
- Me.endCode.Append("Console.ReadKey()")
- Case Else
- MsgBox("Line: " & linenr & vbCrLf & "Reason: Unknown Command (" & token(0) & ")" & vbCrLf, MsgBoxStyle.Critical, "Error")
- Return False
- End Select
- Me.endCode.Append(vbCrLf)
- Return True
- End Function
Puh! Ganz schön viel Code auf einmal, aber schnell erklärt. Wir überprüfen hier einfach den Token der uns übergeben wird und übersetzen ihn in VB Code. Im falle von PRINT brauchen wir noch eine schleife, denn wenn wir diese weglassen würden, würde bei „PRINT Hallo Welt“ nur das Hallo ausgegeben werden. Auch hab ich gleich eine überprüfung eingebaut ob sich im PRINT Befehl eine Variable befindet. Eine Variable wird mit $ erkannt. Ein Beispiel:
Damit können wir jetzt Theoretisch schon Programmieren.
Der WAIT Befehl sollte zum Schluss immer da stehen, sonst schließt sich die Konsole später zu schnell und man kann ihren Inhalt nicht lesen.
Die Funktion compile() geb ich euch jetzt einfach vor, denn es würde zu lange dauern euch den VBCodeProvider genau zu erklären. Dafür gibt’s schon genug Tutorials.
Also hier die Letzte Funktion:
VB.NET-Quellcode
- Private Function compile()
- Dim c As VBCodeProvider = New VBCodeProvider
- Dim icc As ICodeCompiler = c.CreateCompiler()
- Dim cp As CompilerParameters = New CompilerParameters
- cp.GenerateExecutable = True
- cp.GenerateInMemory = False
- Dim exeName As String = String.Format("{0}\{1}.exe", System.Environment.CurrentDirectory, Me.ExeName)
- cp.OutputAssembly = exeName
- Dim source As New StringBuilder()
- source.Append("Imports System" & vbCrLf)
- source.Append("Module Module1 " & vbCrLf)
- source.Append("Sub Main()" & vbCrLf)
- source.Append(Me.endCode.ToString & vbCrLf)
- source.Append("End Sub " & vbCrLf)
- source.Append("End Module " & vbCrLf)
- Dim cr As CompilerResults = icc.CompileAssemblyFromSource(cp, source.ToString())
- If cr.Errors.Count > 0 Then
- Dim er As String
- For Each err As CompilerError In cr.Errors()
- er += err.ToString() + vbCrLf
- Next
- Return er
- End If
- Return cr.PathToAssembly
- End Function
In dieser Funktion wird euer bisheriger Code in eine Standard Konsolen Anwendung geschrieben und diese dann zu einem Programm Compeliert. Das fertig Compelierte Programm sollte dann im selben Ordner erscheinen wie euer Compiler (Bei einem Testdurchlauf wahrscheinlich in DEBUG Ordner).
Zum Schluss noch einmal die gesamte Klasse:
VB.NET-Quellcode
- Imports System.Text
- Imports System.CodeDom.Compiler
- Public Class Compiler
- Private linenr As Integer = 0
- Private endCode As StringBuilder
- Private ExeName As String = "App"
- Public Function start(ByVal Code As String, Optional ByVal name As String = "App") As String
- Me.endCode = New StringBuilder("")
- Me.ExeName = name
- parse(Code.Split(vbCrLf).ToList)
- Return compile()
- End Function
- Private Function parse(ByRef Tokens As List(Of String))
- If Tokens.Count = 0 Then Return False 'Token vorhanden
- Dim TokenStream As New IO.MemoryStream()
- Dim buffer() As Byte
- For Each ThisString As String In Tokens
- buffer = System.Text.Encoding.UTF8.GetBytes(ThisString.Trim(" "))
- TokenStream.Write(buffer, 0, buffer.Length)
- TokenStream.WriteByte(13)
- Next
- TokenStream.Position = 0
- Dim SourceParser As New Microsoft.VisualBasic.FileIO.TextFieldParser(TokenStream)
- Dim TokenFields() As String = {}
- SourceParser.Delimiters = {",", ", ", " "} 'Trennzeichen
- SourceParser.HasFieldsEnclosedInQuotes = True
- While Not SourceParser.EndOfData
- linenr = SourceParser.LineNumber
- Try
- TokenFields = SourceParser.ReadFields 'Versuchen den nächsten Token zu lesen
- Catch ex As Exception 'Wenn ein Fehler auftritt
- MsgBox("Error Message: " & ex.Message & vbNewLine, MsgBoxStyle.Critical, "Error") 'Fehler ausgeben
- TokenStream.Close() 'Tokenstream Schließen
- TokenStream = Nothing
- Exit While
- End Try
- If CompileToken(TokenFields) = False Then Return False 'Bei falschen Token Compelierung beenden
- End While
- Return True
- End Function
- Private Function CompileToken(ByVal token() As String)
- Select Case token(0)
- Case "PRINT"
- Dim buf As String
- For i As Integer = 1 To token.Length - 1
- If (token(i).Substring(0, 1) = "$") Then
- buf += Chr(34) & " & " & token(i).Substring(1) & " & " & Chr(34)
- Else
- buf += token(i) & " "
- End If
- Next
- Me.endCode.Append("Console.WriteLine(" & Chr(34) & buf & Chr(34) & ")")
- Case "GET"
- Me.endCode.Append("Dim " & token(1) & " as String = Console.ReadLine()")
- Case "WAIT"
- Me.endCode.Append("Console.ReadKey()")
- Case Else
- MsgBox("Line: " & linenr & vbCrLf & "Reason: Unknown Command (" & token(0) & ")" & vbCrLf, MsgBoxStyle.Critical, "Error")
- Return False
- End Select
- Me.endCode.Append(vbCrLf)
- Return True
- End Function
- Private Function compile()
- Dim c As VBCodeProvider = New VBCodeProvider
- Dim icc As ICodeCompiler = c.CreateCompiler()
- Dim cp As CompilerParameters = New CompilerParameters
- cp.GenerateExecutable = True
- cp.GenerateInMemory = False
- Dim exeName As String = String.Format("{0}\{1}.exe", System.Environment.CurrentDirectory, Me.ExeName)
- cp.OutputAssembly = exeName
- Dim source As New StringBuilder()
- source.Append("Imports System" & vbCrLf)
- source.Append("Module Module1 " & vbCrLf)
- source.Append("Sub Main()" & vbCrLf)
- source.Append(Me.endCode.ToString & vbCrLf)
- source.Append("End Sub " & vbCrLf)
- source.Append("End Module " & vbCrLf)
- Dim cr As CompilerResults = icc.CompileAssemblyFromSource(cp, source.ToString())
- If cr.Errors.Count > 0 Then
- Dim er As String
- For Each err As CompilerError In cr.Errors()
- er += err.ToString() + vbCrLf
- Next
- Return er
- End If
- Return cr.PathToAssembly
- End Function
- End Class
5. Abschließende Worte
Ich hoffe ich konnte euch einen guten Einblick in den Aufbau eines Compiler geben, muss aber sagen das wir hier nur den Simpelsten Compiler überhaupt gebaut haben, also nicht gleich überall damit Brüsten ;). Dann wünsche ich euch noch viel Spaß mit euren eigenen Compiler.
Zum Schluss noch:
Dieses Tutorial darf nicht ohne Erlaubnis des Autors verbreitet werden
Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „Madd Eye“ ()