PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : [Tutorial] Interpreter-Sprache mit Delphi



Crash-Override
18.02.2005, 18:08
Tutorial
Interpreter-Sprache entwickeln
Mittels der verwendung von Delphi

#o1 - Für wen?
Für wen ist den dieses Tutorial überhaupt geeignet? Ich denke es ist für Delphi Fortgeschrittene bis Delphi Profis geeignet.
Diesem Tut wird KEIN Beispiel-Download Quelltext beiliegen. Ich werde aber einige Code-Beispiele einbringen, sodas es etwas einfacher fällt. Solltest du dich noch wenig mit Delphi auskennen empfehle ich dir zuerst ein paar andere Tutorials durchzugehen und dich hier in einem halben Jahr wieder einzufinden.

#o2 - Einige Überlegungen
Zuerst einmal stellt sich die Frage was für einen Syntax man verwenden will. Ich beziehe mich in diesem Tutorial dafür der Einfachkeit halber an einem Basic ähnlichen Syntax für die neue Interpreter-Sprache. Zum Eingeben werden wir ein RichEdit Feld verwenden und für die Ausgabe (also fürs Debuggen) nehmen wir ein Memo Feld. Das und ein Knopf dürften fürs erste ausreichen.

http://pic2005.milten.lima-city.de/Suck/PNG11.png (http://pic2005.milten.lima-city.de/Suck/PNG10.png)
Bild: Beispiel Anordnung

#o3 - How to Run it?
Nun kommt der spannende Teil, das interpretieren. Dafür fügen wir im Start-Button.OnClick Code zuerst die Variable i (Integer) und die Variable Exit(Boolean) hinzu.

i: Diese Variable wird unsere Zählvariable. In ihr steht immer in welcher Reihe wir gerade interpretieren (Achtung, ihr wisst ja man zählt ab 0, also Zeile 1 ist i dann 0!)

Exit: Falls der Interpretier-vorgang vor dem Ende aller Zeilen abgebrochen werden muss (Fehler) steht diese Variable auf True;

Nun fügen wir diesen Code in das OnClick Ereigniss hinzu.


Exit := False;
i := -1;
repeat
i := i + 1;
Exit := Make(Trim(Code.Lines[i]));
until (i >= Code.Lines.Count-1) OR (Exit = True);


Wie ihr seht wird hier die Funktion make aufgerufen die uns mittels True oder False mitteilt ob der Interpretier-Vorgang der aktuellen Zeile erfolgreich war. Das wird dann der Variable Exit zugewiesen. Ist Exit False war alles in Ordnung, wenn nicht, dann nicht.

#o4 - Funktion Make, unser Herzstück.
Diese Funktion habe ich so:


function Make(S: String): Boolean;
Begin
Result := True;
if get(1,5,uppercase(s)) = 'PRINT' then
Begin
ShowMessage(Trim(get(6,Length(s),s)));
Result := False;
End;
End;

Aufgebaut. Als Übergabe erhält Make die aktuelle Zeile als String.
Nun wird erst mal der Rückgabewert als True (ein Fehler) eingestellt. (Falls es keinen gibt, dann wird dies später rückgängig gemacht...) nun lesen wir mit get die ersten 5 Zeichen der übergebenen Zeile aus und wenn sie 'Print' lauten geben wir alle Zeichen zwischen 6 und dem Ende der Übergabe-Zeile aus. Dann setzen wir den Rückgabewert auf False (Kein Fehler, alles in Ordnung.) und widerrufen somit die Aktion von vorhin. Denn Falls die Zeile nicht Print heißt, dann bleibt es beim Fehler.

#o5 - Funktion get


function get(Anfang, Ende: Integer; S: String):String;
var
Temp: Integer;
error: Boolean;
Begin
Temp := Ende - (Anfang);
error := false;
if Temp < 1 then
error := True;
if Temp > Length(s) then
error := True;
if error = False then
for Anfang := Anfang to Ende do
Begin
Result := Result + S[Anfang];
End;
End;
Diese Funktion möchte ich hier nicht näher dokumentieren, da das Delphi-Standart sein sollte.

#o6 - Testlauf...
Wenn ihr alles Korrekt gemacht habt, dann dürfte es etwa so klappen:
http://pic2005.milten.lima-city.de/Suck/PNG13.png (http://pic2005.milten.lima-city.de/Suck/PNG12.png)

Wenn nicht, dann solltet ihr alle snoch mal durchgehen.

#o7 - Bei neuer Zeile und Kommentar...
... beendet sich das ganze :(

Ändern wir das :)


if get(1,2,uppercase(s)) = '//' then
Begin
Result := False;
End;

if s = '' then
Begin
Result := False;
End;

Das ganze kommt selbstredend in die Make Funktion...

Nun sollte dieser Quellcode lauffähig sein:

Print Hallo Welt

//Kommentar!!!!!

#o8 - Debug-Anzeige
Wir wollen ja wissen in welcher Zeile es zu einem Fehler kam bzw. das alles in Ordnung ist...

Dafür ändern wir den Quelltext von Start-Button.OnClick wie folgt:

Console.Text := '';
Console.Lines.Add('### Interpreter mit Delphi 1.0 ###');
Exit := False;
i := -1;
repeat
i := i + 1;
Exit := Make(Trim(Code.Lines[i]));
until (i >= Code.Lines.Count-1) OR (Exit = True);
if Exit then
Console.Lines.Add('Fehler in Zeile ' + inttostr(i+1) + ' ['+Code.Lines[i]+']')
Else
Console.Lines.Add('Quellcode war in Ordnung!');

Das ganze löscht erst mal allen Text aus der Console und gibt dann den Interpreter-Namen aus. Nun wird der Quellcode ausgeführt. Ist dies Beendet, überprüft er ob Exit True ist (Durch Fehler Beendet) ist dem so gibt er eine Fehlernachricht aus, ist dem nicht so, dann lässt er es.

#o9 - Abschluss
Habt ihr das hier wirklich durchgearbeitet, dann habt ihr für den Anfang ein wirklich tolles System entwickelt. Klar, es ist sehr erweiterungsfähig, aber noch mehr Infos wären glaube ich zuviel. Bitte schreibt doch was ihr hiervon haltet und wenn ihr wollt, dann erweitert den Source.
Wenn ihr wollt schreibe ich das ganze gerne Weiter. Ich kann zum Beispiel was über Variablen bringen (Zuweisung, Ausgabe und Überwachung)....
Und bitte: Nur weil ich sage das hier wäre nicht für Anfänger, traut euch auch mal Fragen zu stellen... Es ist nicht so leicht wies aussieht, ich habe das auch manchmal das ich was einfach so nicht kapiere. Ich erklärs gerne. Schreibt einfach hier n' Post,schreibt mich unter ICQ an (Profil) oder per PN.

Euer Crash-Override

MuadDib
18.02.2005, 20:16
Nettes Tutorial. Tut mir leid, dass ich das von der Einsatzweise von Delphi nicht beurteilen kann, da ich die Sprache nicht gut kenne und ehrlich gesagt auch nicht viel von derartig stark IDE abhängigen Sprachen halte, und ich auch sicher nicht vorhabe, mich damit noch mehr auseinanderzusetzen.

Zum algorithmischen Aspekt kann ich nur sagen, dass ich denke, dass das ganze nicht unbedingt stark erweiterbar ist. So wie ich das sehe, schmeisst du ja Scanner, Syntax-Parser und Interpreter in eine Funktion, in der du die Befehle scannst (Zeichen 0 bis x) und mit einem String-Compare parst. Danach folgt gleich die Interpretation. Wie siehts bei deinem Approach mit etwas komplexeren Strukturen aus, wie zum Beispiel Schleifen, bedingten Sprüngen, etc? Oder einfach nur Variablenzuweisungen, etc.

Ich lasse mich gern eines besseren Belehren, aber ich glaube, dass du hier sehr schnell an Grenzen stösst, die ev. noch unterhalb des LOGO Interpreters liegen :)

Ansonsten wirklich gut aufbereitetes Tutorial, schön geschrieben und gut erklärt.

Crash-Override
18.02.2005, 22:13
Och, ich denke das ist noch schön erweiterbar bei ICE (Gemeinschaftsprojekt von mir und Blackey) warn wir schon bei IF's Varis (unendliche ;) und Eingabefeld (What's youre Name Cowboy? -Jony) und auch Datumsangaben [Format: HH:MM:SS]... War ganz witzig, aber da war alles in einer Funktion und somit extrem Lang, unübersichtlich und nicht mehr Verwaltbar... Ich schreib nun an Tut 2: Variabeln und IF's wenn's klappt...

Edit @Muad
Ok, ich poste es gleich mal... allerdings haben wir es bei unserem Projekt etwas anders gelöst, und zwar durch ein dynamisches und dadurch unendliches Array. Hier poste ich es mit einem festen mit der Größe 100...

MuadDib
19.02.2005, 08:17
Ich schreib nun an Tut 2: Variabeln und IF's wenn's klappt...
Oh ja, bitte. Würde mich interessieren wie ihr das bei eurem Projekt gelöst habt.

Crash-Override
19.02.2005, 10:46
Tutorial
Interpreter-Sprache entwickeln (Teil II)
Mittels der Verwendung von Delphi

#1o – Print erweitern
Das Print war ja, meiner Meinung nach nicht so der Bringer. Viel Edler wäre etwas wie Print ’TEXT’; oder so was. Könnt ihr haben! Nehmen wir die Funktion ParsePrint


function ParsePrint(s:String):String;
var
i: Integer;
Begin
for i := 1 to Length(s) do
Begin
if s[i] <> '''' then
if s[i] <> '"' then
if s[i] <> ';' then
Result := Result + s[i];
End;
End;


Sie bearbeitet den String. Nun editieren wir in der Make den Print-Befehl zu:


if get(1,5,uppercase(s)) = 'PRINT' then
Begin
ShowMessage(Trim(ParsePrint(get(6,Length(s),s))));
Result := False;
End;


#11 – Variabel-Support
Variabeln zu nutzen ist nicht so einfach wie man denkt. Dafür müssen wir zuerst einen neuen Type erstellen:


TVari = record
Name: string;
Wert: string;
end;


(Types werden direkt unter type(unter den uses) deklamiert)

Mittels dieses Types ist es einfach eine Variable mit dem Type Vari zu erstellen, da wir allerdings mehrer Brauchen verwenden wir ein Array. Wir deklamieren es also Global.


Variable: array[1..100] of TVari;


#12 – Variable mit Wert füllen
Im letzen Schritt wurde der Support für Variabeln hinzugefügt, diesmal wollen wir eine Variable mit einem Wert beschreiben.


function Variable(Name: String; Wert: String): Boolean;
var
i: Integer;
Exit: Boolean;
Begin
i := 0;
repeat
i := i + 1;
if frmMain.Variable[i].Name = Name then
Begin
Exit := False;
frmMain.Variable[i].Wert := Wert;
End;
until (i = 100) OR (Exit = True) OR (frmMain.Variable[i].Name = '');
if frmMain.Variable[i].Name = '' then
Begin
frmMain.Variable[i].Name := Name;
frmMain.Variable[i].Wert := Wert;
End;
Result := False;
if (i >= 100) AND (frmMain.Variable[i].Name <> Name) then
Begin
Result := True;
frmMain.Console.Lines.Add('Es sind schon zu viele Variabeln in Verwendung!');
frmMain.Console.Lines.Add('Es dürfen Maximal 100 Variabeln in dieser Version verwendet werden!');
End;
End;


(Euer Formularname = frmMain)

Im Groben kann man sagen das das Teil solange alle Variabeln durchgeht bis entweder
a) Die Variable gefunden wird, dann wird der Wert zugewiesen
b) Irgendwann Leere Varinamen in der Liste auftauchen, das heißt die Vari steht noch nicht in der Liste, also fügen wir sie hinzu
c) Keine der 100 Varis diesen Namen trägt, also geben wir einen Fehler aus.

So, aber jetzt zur Nutzung:

In Make:


if uppercase(S[1]) = '$' then
Begin
Result := Variable(get(2,FindEndVar(s),s),get(FindEndVar(s)+2,Length(s),s));
End;


So, nun brauchen wir noch die Funktion FindEndVar. Diese Funktion soll herrausfinden wo der Variabelname in der Zuorndung aufhört(vor ’=’):


function FindEndVar(S: String): Integer;
var
i: Integer;
Begin
Result := -1;
for i := 1 to Length(s) do
Begin
if (s[i] = '=') AND (Result = -1) then
Result := i-1;
End;
End;


So, das Variabelzuweisen sollte nun klappen.

#13 – Variable Ausgeben
Ich weiß, das das nicht leicht ist, aber ich habe euch am Anfang gewarnt und wenn ihr hier seid, dann nennt euch echte 31337-Delphianer ;)

So, zurück zum Problem: Der Ausgabe einer Variable.

Wir nehmen die Funktion PrintVari dafür:


function PrintVari(S: String): String;
var
i: Integer;
First: Integer;
Last: Integer;
t: String;
Begin
i := 0;
t := s;
First := 0;
Last := 0;
repeat
i := i + 1;
if s[i] = '$' then
First := i;
if (s[i] = ';') AND (First <> 0) then
Begin
Last := i;
t := StringReplace(t, FindPrint(get(First,Last,t)), VariableFind(FindPrint(get(First+1,Last-1,t))), [rfIgnoreCase]);
First := 0;
Last := 0;
End;
until i >= Length(s);
Result := t;
End;



Die Funktion sucht in einem String nach einem $ und liest den Namen der gemeinten Variable aus und ersetzt ihn schließlich durch dessen Wert.

Nun noch die Funktion VariableFind die für uns den Wert rauskramt.


function VariableFind(Name: String): String;
var
i: Integer;
Exit: Boolean;
Begin
i := 1;
Result := '';
repeat
if frmMain.Variable[i].Name = Name then
Begin
Exit := True;
Result := frmMain.Variable[i].Wert;
End;
until (i = 100) OR (Exit = True) OR (frmMain.Variable[i].Name = '');
if Result = '' then
Begin
frmMain.Console.Lines.Add('Error, Variable '+Name+' is not declameted!');
frmMain.Kill := True;
End;
End;


So, dann nur noch die Print-Anweisung in Make so ersetzen:


if get(1,5,uppercase(s)) = 'PRINT' then
Begin
ShowMessage(Trim(ParsePrint(PrintVari(get(6,Length(s),s)))));
Result := False;
End;


So, und nun zum Test:


$name=Crash
Print ’Hallo, $name;’;

#14 – Variabel-Support beenden nach Run
Damit vor jedem Run die Variabeln aus dem Array gelöscht werden setzt das hier an den Anfang des Start-Button.OnClick Ereignisses:


for i := 1 to 100 do
Begin
Variable[i].Name := '';
Variable[i].Wert := '';
End;

#15 – Ende
Für die IF’s hat’s diesmal wieder nicht gereicht, doch ich hoffe ihr könnt mit diesen Variabel-Funktionen was anfangen. Dieses Mal gebe ich bewusst einen Beispiel-Download-Quellcode mit, da ich denke da es diesmal echt nicht einfach war. Der Quellcode ist compilebar, dennoch hoffe ich das ihr sofern ihr ihn verwendet mich wenigstens Informiert und mich dabei erwähnt.

=> Beispielquellcode (http://xo5bg.xardas.lima-city.de/Beispiel.rar)