Ergebnis 1 bis 5 von 5

Thema: [Tutorial] Interpreter-Sprache mit Delphi

  1. #1

    [Tutorial] Interpreter-Sprache mit Delphi

    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.


    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.
    Code:
      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:
    Code:
    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
    Code:
    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:


    Wenn nicht, dann solltet ihr alle snoch mal durchgehen.

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

    Ändern wir das

    Code:
      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:
    Code:
    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:
    Code:
      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

  2. #2
    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.

    Geändert von MuadDib (18.02.2005 um 19:29 Uhr)

  3. #3
    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...

    Geändert von Crash-Override (19.02.2005 um 09:44 Uhr)

  4. #4
    Zitat Zitat von Crash-Override
    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.

  5. #5
    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
    Code:
    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:
    Code:
      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:
    Code:
      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.
    Code:
    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.
    Code:
    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:
    Code:
      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 ’=’):
    Code:
    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:
    Code:
    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.
    Code:
    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:
    Code:
      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:
    Code:
    $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:
    Code:
      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

Berechtigungen

  • Neue Themen erstellen: Nein
  • Themen beantworten: Nein
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein
  •