Entwickler-Ecke

Sonstiges (Delphi) - Befehlsinterpreter - goto - wie springen?


galagher - Di 19.05.09 17:35
Titel: Befehlsinterpreter - goto - wie springen?
Hallo!

Ich bin dabei, einen Befehlsinterpreter wie COMMAND.COM (nur besser :wink: ) zu schreiben, und nun hänge ich beim goto.
Das Prinzip ist einfach: Ich lade die abzuarbeitende Datei in eine TStringList und gehe Zeile für Zeile durch:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
echo 1  //es soll 1 ausgegeben werden

goto END  //überspringe alle weiteren "echo"-Befehle ...

echo 2
echo 3

:END  //... und gehe da hin
echo end  //es soll end ausgegegben werden

Das Ganze läuft in einer for-Schleife. Daher ist es einfach, zeilenweise zu arbeiten, aber wie stellt man den for-Zähler höher? Wie könnte man das machen?


jaenicke - Di 19.05.09 17:40

Den Schleifenzähler verändern ist nicht möglich. Und dein Konzept mit Goto sieht auch sehr sehr unübersichtlich und vermutlich unnötig langsam aus.

Was du machen kannst ist mit Continue zum nächsten Durchlauf der Schleife zu gehen, also den Rest des aktuellen Durchlaufs zu überspringen.


galagher - Di 19.05.09 17:53

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Den Schleifenzähler verändern ist nicht möglich.
Ich weiss. Das ist es ja - man braucht irgendwie einen zweiten Zähler...

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Und dein Konzept mit Goto sieht auch sehr sehr unübersichtlich und vermutlich unnötig langsam aus.
In Stapeldateien gibt es aber nun mal goto, und - na klar ist das unübersichtlich! Und was langsam betrifft - bei meinetwegen 20 Zeilen Stapeldatei-Code ist was wohl egal! Abgesehen davon, interpretiert mein Programm sowieso jeden Befehl, und interpretierter Code ist immer langsam(er)!

Ich meine das so:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
  {Die Liste wcdList zeilenweise abarbeiten}
  for i := 0 to wcdList.Count-1 do
  begin
  //if GetWord(LowerCase(wcdList[i]), 1) = 'goto' then
  //ja - was hier tun? Genau das ist ja meine Frage!
   
   try
    if not (wcdList[i] = ''then  //Leerzeilen ignorieren
    begin
     if not (wcdList[i][1] = ';'and not  {Anmerkungen und ...}
      (GetWord(LowerCase(wcdList[i]), 1) = 'rem'and not
      (wcdList[iwcdline][1] = ':'then    {... Kennungen (für goto) nicht ausgeben,}
       RxRichEdit1.Lines.Add(wcdList[i]);  {alle anderen Einträge schon!}

     if not (wcdList[i][1] = ':'then
      CommandExecute(wcdList[i]);  {Befehl mit Prozedur CommandExecute ausführen}
    end;  {end von "if not (wcdList[i] = '') then"}

   except
    ErrorText(sGeneralError);
    break;
   end;
  end;


user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Was du machen kannst ist mit Continue zum nächsten Durchlauf der Schleife zu gehen, also den Rest des aktuellen Durchlaufs zu überspringen.
Habe continue bisher nicht mal gekannt...


jaenicke - Di 19.05.09 17:57

user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
In Stapeldateien gibt es aber nun mal goto
:oops:
Ich hatte das goto als dein Vorhaben zur Umsetzung in Delphi verstanden. Dass es in Batchdateien benutzt wird, ist klar. Tut mir leid.

Was das Problem betrifft:
Wie wäre es mit einer while-Schleife? ;-)

Da kannst du den Schleifenzähler beliebig verändern, wobei das dann auch kein Zähler, sondern ein Zeiger auf die aktuelle Zeile wäre. Und genau das brauchst du ja.
Und sobald du über die letzte Zeile hinaus bist, bricht die Schleife ab.


Dunkel - Di 19.05.09 17:59

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Den Schleifenzähler verändern ist nicht möglich.

Nichts ist unmöglich!


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
var 
  i: Integer; 
begin
  for i := 1 to 20 do 
    begin
      Inc(Integer(Pointer(@i)^));
      // so nimmt i nur gerade Werte an, da eine Zeile höher mit ein wenig Pointer-Unsinn die Schleifenvariable händisch um 1 erhöht wird, zusätzlich zur automatischen Schleifen-Inkrementierung
    end;


Schön ist das nicht, führt zu nahzu unwartbaren Code, weil nicht auf dem ersten Blick erkennbar ist, was wirklich passiert, und überhaupt und sowieso. Vergesst das am Besten wieder ganz schnell, bloß NIE benutzen!! :?


jaenicke - Di 19.05.09 18:01

Ja, mit solchen Tricks kommt man auch teilweise an private Properties usw., und mit Assembler in die Schleife eingestreut geht das natürlich auch. :mrgreen:


BenBE - Di 19.05.09 18:37

Welche Programmiersprache brauch denn GOTOs??? Nur Come From ist das Wahre!

Und zu deinem Problem: Befehlsinterpreter sollten nicht mit einer FOR-Schleife arbeiten, sondern mit einem Instruction Pointer, der bei Sprüngen ggf. angepasst wird.


galagher - Di 19.05.09 18:53

Meine Lösung sieht nun Folgendes vor: Der unten stehende Code ist die Schleife, die die Datei zeilenweise abarbeitet, aber es gibt auch eine andere Prozedur mit Namen _goto, die die Integer-Variable g ändert. Leider komme ich auch im Delphi-Code nicht ohne goto aus, denn irgendwie muss ich ja erreichen, dass die Schleife erneut mit dem neuen g-Wert beginnt!


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
  g := 0;  {g ist eine global Interger-Variable}

  WCD:

  for i := g to wcdList.Count-1 do
  begin
    if not (wcdList[i] = ''then
    begin
     try
      if not (wcdList[i][1] = ';'and not  {Anmerkungen und ...}
             (GetWord(LowerCase(wcdList[i]), 1) = 'rem'and not
             (wcdList[i][1] = ':'then  {... Kennungen (für goto) nicht ausgeben,}
       RxRichEdit1.Lines.Add(wcdList[i]);  {alle anderen Einträge schon!}

      if not (wcdList[i][1] = ':'then
       CommandExecute(wcdList[i]);

     except
      ErrorText(sGeneralError);
      break;
     end;

     if (GetWord(LowerCase(wcdList[i]), 1) = 'goto'then
      goto WCD;  {dort hat g bereits einen neuen Wert, der in einer Prozedur _goto zugewisen wird}
    end;  {end von "if not (wcdList[i] = '') then"}
  end;  {end von "for i := g to wcdList.Count-1 do"}



Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
procedure _goto(sCmd, sSwitch, sParam: String; iCount: Byte);
var
  i: Integer;
begin
 for i := 0 to wcdList.Count-1 do
 begin
  if ':'+sParam = wcdList[i] then  {sParam ist das Sprungziel}
   begin
    g := i;
    break;
   end;
 end;
end;

... Ja, und richtig gemütlich wird's vermutlich, wenn ich in meinem Programm beim if angelangt bin!


jaenicke - Di 19.05.09 20:10

user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
Leider komme ich auch im Delphi-Code nicht ohne goto aus, denn irgendwie muss ich ja erreichen, dass die Schleife erneut mit dem neuen g-Wert beginnt!
Was hast du denn gegen das bereits angesprochene while? :lol:
Eine for-Schleife ist explizit dafür gedacht möglichst effizient den Wertebereich einmal in einer Richtung durchzugehen. Für alles andere ist diese spezielle Schleife nicht geeignet, dafür gibt es while oder repeat.


BenBE - Mi 20.05.09 09:00

Darf ich Schadensersatz für diese Missbräuchliche Nutzung von goto verlangen???

Um das mal dem Fragesteller zu zeigen, was gemeint ist:


Delphi-Quelltext
1:
2:
3:
4:
5:
While not EndOfProgram do
Begin
    Cmd := GetCmdAt(InstrPtr);
    ProcessCommand(Cmd);
end;


That's it ;-)


galagher - Mi 20.05.09 19:59

Ok, danke euch! Mal sehen, wie ich das goto im Delphi-Code wegbekomme!


ffgorcky - Mi 20.05.09 20:59

Also ich denke, wenn Dein Interpreter GOTO liest, dann müsste er doch nur in der Datei (also Deinem Memo, oder wie auch immer Du das zur Bearbeitung lädst) nach einem Punkt - also einer Zeile - suchen, der auf jeden Fall mit Doppelpunkt anfängt und das Wort beinhaltet, welches hinter goto steht und dort dann weiter arbeitet.
Aber einfacher wäre es, wenn Du das GoTo wirklich ganz weglassen könntest - was aber leider nicht immer so wirklich einfach ist.


Yogu - Mi 20.05.09 21:02

user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
Welche Programmiersprache brauch denn GOTOs??? Nur Come From ist das Wahre!

Das ist einfach nur genial :rofl: Bau ich sofort in meine Scriptsprache ein!

Die ist übrigens auch gar kein so schlechter Vergleich, schau dir mal den Interpreter dieses Tutorials [http://wiki.delphigl.com/index.php/Tutorial_Scriptsprachen_Teil_2#Ein_Emulator_f.C3.BCr_unseren_Prozessor] an. Das Grundprinzip ist dort auch:

Register P zeigt auf den Befehl, der als nächstes interpretiert werden soll. Der Befehl GOTO (dort heißt er JMP) verändert einfach diesen Wert. Um zu wissen, wo sich denn welches Label versteckt, solltest du ganz am Anfang alle Labels in einer Liste bzw. in einem Array sammeln. Dann kannst du ganz einfach diese Liste durchsuchen. Das wäre sicherlich performanter als immer die komplette Datei abzuklappern.


galagher - Do 21.05.09 08:44

user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
Befehlsinterpreter sollten nicht mit einer FOR-Schleife arbeiten, sondern mit einem Instruction Pointer, der bei Sprüngen ggf. angepasst wird.
:?: Wie sieht so etwas denn aus? Ein Beispiel?

user profile iconYogu hat folgendes geschrieben Zum zitierten Posting springen:
Um zu wissen, wo sich denn welches Label versteckt, solltest du ganz am Anfang alle Labels in einer Liste bzw. in einem Array sammeln. Dann kannst du ganz einfach diese Liste durchsuchen. Das wäre sicherlich performanter als immer die komplette Datei abzuklappern.

Mal im Einzelnen: Die Datei ist in einer TStringList, da so zeilenweises Abarbeiten einfach ist - bis auf goto. Dann muss nämlich der Zähler geändert werden, und das erreicht man (zugegebenermassen unelegant), indem man die Liste ab jener Zeile, die das Label enthält, erneut abarbeitet.
Also: es kommt ein goto, das auf ein Sprungziel in der 10. Zeile verweist. Verlasse die Liste und lies sie erneut, diesmal aber nicht ab Zeile 0, sondern ab 9.

Delphi-Quelltext
1:
for i := g to List.Count-1 do  //am Anfang ist g 0, ein goto ändert g auf einen dem Sprungziel entspr. Wert                    


Ok, man kann das "Delphi-Code-goto" vermeiden, indem man den Code umstrukturiert, das kriege ich sicher hin. Aber mich interessiert, wie man das mit dem Instruction Pointer bewerkstelligt.


jaenicke - Do 21.05.09 08:48

user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
:?: Wie sieht so etwas denn aus? Ein Beispiel?
Hat er doch schon geschrieben. ;-)
Du nimmst eine Integervariable, die dir die aktuelle Zeile angibt und setzt die auf 0. Danach gehst du in einer while-Schleife die Befehle durch und erhöhst die aktuelle Zeile immer um 1. Und bei goto setzt du den Wert auf die entsprechende Zeile, und es geht dort weiter.
Die while-Schleife lässt du laufen, solange die aktuelle Zeile kleiner als die Anzahl der Zeilen ist. Ist die letzte zeile abgearbeitet springt der Zähler eins weiter und ist nicht mehr kleiner, dann bricht die Schleife ab.


BenBE - Do 21.05.09 12:33

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
:?: Wie sieht so etwas denn aus? Ein Beispiel?
Hat er doch schon geschrieben. ;-)

Wenigstens einer liest meine Posts ...
:flehan: :flehan: :flehan:


galagher - Do 21.05.09 19:25

user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
:?: Wie sieht so etwas denn aus? Ein Beispiel?
Hat er doch schon geschrieben. ;-)

Wenigstens einer liest meine Posts ...
:flehan: :flehan: :flehan:

Ja, hast du bereits geschrieben :oops: - und ich habe es bereits umgesetzt - sieht gleich viel besser aus ohne das plumpe goto im Delphi-Code!

Danke!