Forum: PC-Programmierung Einfacher Interpreter in C++


von Papa (Gast)


Lesenswert?

Für meine Malen nach Zahlen Programmieraufgaben (Studium) hab ich einen 
Taschenrechner geschrieben der nach der Umgekehrten Polnische Notation 
arbeitet. (Siehe Wikipedia)
Alles fertig, und funktioniert, jetzt aber eine Fundamentale Frage:
Wie kann ich einen Interpreter (?? heißt das dann so ??) schreiben der 
diesen Text (z.b. als char-array) in untenstehenden Code umsetetzt?

Straight forward wäre der Ansatz mit strings zu arbeiten, geziehlt die 
operatoren * + - / zu ermitteln, die zahlen zu ermitteln, zähler und 
nenner zu extrahieren, CFractions() zu erstellen usw. das wäre eine 
gigantische unübersichtbare geschubserei, mir fällt aber kein 
allgemeiner Ansatz ein so etwas zu machen.
1
"5/2 6/5 * 1/2 + 1/3 - 5 1/6 + 11/4 + /"
1
     pushValue(CFraction(5,2));       // 5/2
2
        pushValue(CFraction(6,5));       // 6/5
3
        multiply();                      // *
4
        pushValue(CFraction(1,2));       // 1/2
5
        add();                           // +
6
        pushValue(CFraction(1,3));       // 1/3
7
        subtract();                      // -
8
        pushValue(CFraction(5,1));       // 5
9
        pushValue(CFraction(1,6));       // 1/6
10
        add();                           // +
11
        pushValue(CFraction(11,4));      // 11/4
12
        add();                           // +
13
        divide();                        // /
14
        popResult(result);
15
        cout << result;
|

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Solche Texte einzulesen und zu Verarbeiten ("parsen") ist eine 
Wissenschaft für sich ("theoretische Informatik"). Lies mal Wikipedia 
über Chomsky-Hierarchie und kontextfreie Grammatik.
Ich habe mal zu Demonstrationszwecken einen sehr einfachen Parser für 
beliebige kontextfreie Grammatiken in ruby (da schön einfach, dürfte 
sich aber auch schmerzfrei in C++ übersetzen lassen) geschrieben, dem 
man die gewünschte Grammatik in einer Art BNF übergibt. Hier ist der 
Sourcecode zu finden: http://games.2g2s.de/?page_id=314
Unten im Code unter der "Parsing-Engine" ist dann eine 
Beispiel-Grammatik für eben solche arithmetischen Ausdrücke und dessen 
einlesen.

von Karl H. (kbuchegg)


Lesenswert?

Die Syntax, die du dir da ausgedacht hast, ist schlecht, weil sie nicht 
LL(1) ist.

Kurz gesagt: wenn du als nächstes Symbol von der Eingabe ein / einliest, 
dann kannst du nicht unterscheiden, ob es sich dabei um eine Division 
oder um einen Bruchstrich für eine CFraction handelt.

Theoretisch (mathematisch) sollte das egal sein, weil ein Bruch auch 
nichts anderes als eine Division ist. Bei dir aber nicht.

d.h. du unterscheidest zwischen
1
  5/2
und
1
  5 2 /

und das ist schlecht. Denn welcher Fall vorliegt kannst du erst 
entscheiden, nachdem du in 5/2 die zweite Zahl gesehen hast. Die 
Ansicht: ja, aber im ersten Fall sind ja keine Leerzeichen vorhanden ist 
keine gute Entscheidung. Auf Leerzeichen zu setzen ist für einen 
Benutzer recht fehleranfällig, da kein Mensch einsieht, warum
1
  5 / 2
und
1
  5/2
bzw
1
  5 /2
verschiedene Dinge sein sollen.
Weiters: Wie ist
1
5 2 / 4 *
korrekt zu interpretieren? Ist das nun
1
         2
2
   5 * -----
3
         4
oder ist es
1
    5
2
   --- * 4
3
    2
deine Schreibweise wäre nicht eindeutig in diesem Punkt, wenn du 
Fractions zulässt.


Ansonsten ist sowas eine Standardübung in der ersten Hälfte der 
Vorlesung "Compilerbau". Alles beginnt damit, dass man die EIngabe 
zunächst in Symbole zerlegt. Man macht sich eine Funktion, die die 
Eingabe verwaltet und bei jedem Aufruf das jeweils nächste Symbol 
liefert. Symbole sind bei dir 'Number, Plus, Minus, Times, Divide' (ich 
ignoriere mal deine Fractions). Da deine Notataion bereits UPN ist und 
du auch UPN erzeugen musst, ist das ganze nicht viel mehr als eine Übung 
in der Stringverarbeitung.

Die Hauptfunktion sieht ungefähr so aus
1
#define numSym     1
2
#define plusSym    2
3
#define minusSym   3
4
#define timesSym   4
5
#define divideSym  5
6
#define eofSym    99
7
....
8
9
char InputString[80];
10
int nextChar;
11
12
....
13
14
  strcpy( InputString, "5 8 *" );
15
  nextChar = 0;
16
17
18
  sym = nextSym( &number );
19
20
  while( sym != eofSym )
21
  {
22
    if( sym == numSym )
23
    {
24
      sprintf( buff, "pushValue(CFraction(%d,1);\n", number );
25
      emit( buff );
26
    }
27
28
    else if( sym == plusSym )
29
      emit( "add();\n" );
30
31
    else if( sym == minusSym )
32
      emit( "subtract();\n" );
33
34
    else if( sym == timesSym )
35
      emit( "multiply();\n" );
36
37
    else if( sym == divideSym )
38
      emit( "divide();\n" );
39
  }
40
41
  emit( "popResult(result);\n" );
42
  emit( "cout << result;\n" );
43
44
....

dann brauchst du noch eine Funktion nextSym, die aus dem Eingabestring 
das nächste Symbol bestimmt.

Das geht so: die Funktion (Klasse) merkt sich, an welcher Stelle im 
String sie zuletzt aufgehört hat.
Dort macht sie beim nächsten Aufruf weiter.
Zunächst werden alle Leerzeichen überlesen. Mit dem nächsten Zeichen im 
String entscheidest du dann: ist es ein '+', '-', '*', '/' dann liefert 
die Funktion das zugehörige Symbol. Ist das nächste Zeichen aber eine 
Ziffer ('0' bis '9'), dann ist das der Anfang einer Zahl. du liest 
solange Zeichen weiter, wie sie Ziffern sind und setzt die Zahl dabei 
zusammen, während die Ziffern auftauchen. Getreu der Vorschrift
1
  Zahl = 10*Zahl + Ziffer - '0';

also ca so
1
int nextSym( int* number )
2
{
3
  while( InputString[nextChar] == ' ' )
4
    nextChar++;
5
6
  nChar = InputString[nextChar++];
7
8
  switch( nChar )
9
  {
10
    case '+':
11
      return plusSym;
12
      break;
13
14
    case '-':
15
      return minusSym;
16
      break;
17
18
    case '*':
19
      return timesSym;
20
      break;
21
22
    case '/':
23
      return divideSym;
24
      break;
25
  }
26
27
  *number = 0;
28
  while( nChar >= '0' && nChar <= '9' )
29
    *number = *number * 10 + nChar - '0';
30
  return numberSym;
31
}

(Fehlerbehandlung bzw. Stringende musst du noch ergänzen.

: Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.