I-PROGR3-HOME

  1. Einführung

  2. Ausgangspunkt: use Case und Klasse

  3. Konstruktion eines Parsers

  4. Schwache Integration in ein C++-Programm

  5. Starke Integration in ein C++-Programm

  6. Übungen


I-PROGR3 WS03 - PROGRAMMIEREN3 - Vorlesung mit Übung
VL7+8: Die Kombination der Werkzeuge 'flex' und 'bison' und deren Interaktion mit einer C++-Klasse

 Achtung : Skript gibt den mündlichen Vortrag nicht vollständig wieder !!!
                         

AUTHOR: Gerd Döben-Henisch
DATE OF FIRST GENERATION: Sept-14, 2003
DATE OF LAST CHANGE: Nov-24, 2003, 0:30h
EMAIL: Gerd Döben-Henisch



1. Einführung

Nachdem nun die Werkzeuge flex und bison einzeln vorgestellt worden sind --bzw. auch schon ihr Zusammenspiel in einem Programm--, soll nun überlegt werden, wie diese Werkzeuge für das in VL3+4 vorgestellte Projekt nutzbar gemacht werden kann. Zur Erinnerung, in VL4 wurden als Methoden der Klasse world unter anderem die Operationen world_do_action( ), world_start_game( ) und world_stopp_game( ) eingeführt, ohne dass bislang geklärt worden ist, wie diese Funktionen zu realisieren seien. Dies ist eine Frage des Designs.

In dieser Vorlesung soll beispielhaft untersucht werden, wie man diese Funktionen mit flex und bison realisieren kann und wie diese Realisierung sich dann in die Klasse world einbinden lässt.


START

2. Ausgangspunkt: Use Case und Klasse

Wir beginnen nochmals bei dem --hier reduzierten-- Use Case, in dem zwei Spieler A (:= weiss) und B (:= schwarz) ein Spiel beginnen, Aktionen ausführen und das Spiel beenden können. Allerdings hat nur der Spieler A (der, mit den weissen Steinen) das Recht, zu beginnen.



I-PROGR3-usage

Reduzierter Use Case



Wie in der Ereignis-Reaktionsliste festgelegt worden ist, soll man nach dem Startbefehl ein Spielbrett mit den Figuren in Grundaufstellung sehen, und nach der Eingabe eines Zuges jeweils die dadurch hervorgerufene Änderung, sofern die Regelkontrolle nicht einen unerlaubten Zug anzeigt. Bei Eingabe von Ende wird das Spiel abgebrochen.

Eine entsprechende reduzierte Klasse world könnte wie folgt lauten:



I-PROGR3-world_class

Reduzierte Klasse 'world'



Daraus ergibt sich, dass die Analyse der Usereingaben so beschaffen sein muss, dass sie eine der drei öffentlichen Funktionen 'start', 'stopp' bzw. 'do_action' erkennen kann.


START

3. Konstruktion eines Parsers

Ausgangspunkt für die Konstruktion eines Parsers ist einmal die Eingabe des Benutzers, die hier als Zeichenkette angenommen wird, und die 'Vorgabe, dass diese Zeichenketten in die Methoden world_do_action( ), world_start_game( ) und world_stopp_game( ) abgebildet werden müssen.

Das nachfolgende Schaubild zeigt eine mögliche Analyse der Zeichenketten:



I-PROGR3-parsererzeug

Erzeugung eines Parsers



In dieser Analyse wird angenommen, dass die Zeichenketten vom Benutzer sich in drei Kategorien einordnen lassen: START, ENDE sowie ZUG. Während START und ENDE jeweils nur durch die worte 'start' und 'ende' realisiert werden, kann ein ZUG mehrere variable Elemente umfassen. Ein ZUG besteht aus FELDERN, die entweder nur einen einfachen ZUG repräsentieren oder einen zusammengesetzten ZUG.

Während die Grundereignisse START, ENDE, ZUG, KOPF, FORTSETZUNG und LF im Rahmen der lexikalischen Analyse von flex erkannt werden können, wird die Kombination dieser Ereignisse und deren Zuordnung zu den gewünschten Methoden im Rahmen der syntaktischen Analyse von bison geleistet.

Betrachten wir zunächst die lexikalische Analyse, die in der ersten Fassung mit einem flex-Programm realisiert wird, das als 'stand alone' Programm konzipiert ist, so dass man es leichter testen kann.


/***********************+
*
* checker1.l
*
*************************/

%{
 #include <stdio.h>
%}

LF  \n
KOPF :
FORTSETZUNG ,
FELD [a-h][1-8]
START start
ENDE ende

%%
[ \t]

{START}    printf("START \n ");
{ENDE}    printf("ENDE \n ");

{FELD}    printf("FELD ");
{KOPF}    printf("KOPF ");
{FORTSETZUNG}    printf("FORTSETZUNG ");
{LF}    printf("LF \n");

%%

int main(){

yylex();
return(1);
}


In diesem kleinen Programm werden zunächst die Makros LF, KOPF, FORTSETZUNG, FELD, START und ENDE definiert. Diese Makros werden dann anschliessend im Rahmen der eigentlichen fflex-Regeln benutzt, um diese übersichtlicher hinschreiben zu können.

Nachstehend die Kompilierung der 'stand alone'-Version und ein kleiner Test:


gerd@kant:~/public_html/fh/I-PROGR3/I-PROGR3-TH/VL7> flex checker1.l
gerd@kant:~/public_html/fh/I-PROGR3/I-PROGR3-TH/VL7> gcc -o checker1 lex.yy.c -lfl

gerd@kant:~/public_html/fh/I-PROGR3/I-PROGR3-TH/VL7> ./checker1

LF
:
KOPF LF
,
FORTSETZUNG LF
a1
FELD LF
a1:b2
FELD KOPF FELD LF

Für das Zusammenspiel mit bison muss man dieses erste flex-Programm erweitern um return-Statements, die dem aufrufenden Programm --in diesem Fall yyparse(), das durch bison erzeugt wird--, mitteilen, welche der verschiedenen in flex definierten Zeichenmustern ('pattern') im Input gerade auftreten. Da die return()-Fuktion als Argument einen int-Wert verlangt, muss man die Makros LF, KOPF, FORTSETZUNG etc. mittels #define mit solch einem int-Wert definieren. Dies kann man automatisch erzeugen lassen, indem man bei dem Aufruf von bison die Option '-d' angibt; dann erzeugt bison die Datei 'name.tab.h', wobei 'name' der name der bison-Programmdatei ist.

Schauen wir uns dies an einem Beispiel an. Als erstes muss das flex-Programm um die return-Anweisungen erweitert werden. Dazu muss dann auch eine #include-Datei angegeben werden, in der die Makros definiert werden. Dies ist in diesem Fall die Datei checker1.tab.h, da diese aus checker1.y mittels bison -d checker1.y automatisch als 'Abfallprodukt' erzeugt wird. Denn die eigentliche Datei, die mit bison -d checker1.y erzeugt wird, ist die Datei checker1.tab.c. Im Anhang des flex-Programms wird auch noch eine kleine Error-Funktion eingebaut, die von yyparse() aufgerufen wird, falls es einen Feler gibt.



I-PROGR3-flexbison

flex + bison: Version 1






/***********************+
*
* checker2.l
*
*************************/

%{
 #include <stdio.h>
 #include "checker1.tab.h"
%}

LF  \n
KOPF :
FORTSETZUNG ,
FELD [a-h][1-8]
START start
ENDE ende

%%
[ \t]

{START}   {  printf("START \n"); return(START); }
{ENDE}    { printf("ENDE \n "); return(ENDE); }
{FELD}    { printf("FELD "); return(FELD); }
{KOPF}    { printf("KOPF "); return(KOPF); }
{FORTSETZUNG}    { printf("FORTSETZUNG "); return(FORTSETZUNG); }
{LF}    { printf("LF \n"); return(LF); }

%%

     void
     yyerror (const char *s)  /* called by yyparse on error */
     {
       printf ("%s\n", s);
     }  



In dem bison-Programm checker1.y werden die Meldungen aus der lexikalischen analyse registriert und es muss hier festgelegt werden, (i) wie die lexikalischen Einheiten kombiniert werden dürfen und (ii) welche Aktionen bei Auftreten dieser Kombinationen gestartet werden sollen. Im vorliegenden Fall werden nur die drei Typen von Befehlen unterschieden und in Abhängigkeit davon, werden die Aktionen 'start', 'stopp' und 'do_action' bestimmt.




/***********************+
*
* checker1.y
*
*************************/

%{
 #include <stdio.h>
 #include "lex.yy.c"
%}

%token START ENDE FELD KOPF FORTSETZUNG LF

%%
input:    /* empty */
          | input line  { printf("bison: INPUT \n"); }
     ;

line : LF    { printf("bison: LF \n"); }
     | befehl LF      { printf("bison: BEFEHL \n"); }
;

befehl : START    { printf("bison: START \n"); }
       | ENDE     { printf("bison: STOPP \n"); }
       | zug    { printf("bison: DO_ACTION \n"); }
;

zug : FELD KOPF FELD        /* { printf("bison: EINFACHER ZUG"); } */
    | zug FORTSETZUNG FELD  /* { printf("bison: KOMPLEXER ZUG"); } */
;

%%




Die c++-Datei zum Testen ist zunächst gaz einfach; sie ruft nur die Funktion yyparse() auf, die in der Datei checker1.tab.c definiert wird.




/***********************+
*
* checker1_usage.cpp
*
*************************/

#include <iostream>

using namespace std;

int main(){

  extern int yyparse();
  extern int yylex();

  yyparse();

}



Hier die Kompilierung und ein kleiner Test.

gerd@kant:~/public_html/fh/I-PROGR3/I-PROGR3-TH/VL7> flex checker2.l  (---> lex.yy.c)
gerd@kant:~/public_html/fh/I-PROGR3/I-PROGR3-TH/VL7> bison -d -t checker1.y (---> checker1.tab.c, checker1.tab.h)
gerd@kant:~/public_html/fh/I-PROGR3/I-PROGR3-TH/VL7> g++ -o checker checker1_usage.cpp  checker1.tab.c  -lfl

gerd@kant:~/public_html/fh/I-PROGR3/I-PROGR3-TH/VL7> ./checker
start
START
bison: START
LF
bison: BEFEHL
bison: INPUT
a1:b2,c3
FELD KOPF FELD FORTSETZUNG FELD LF
bison: DO_ACTION
bison: BEFEHL
bison: INPUT
ende
ENDE
 bison: STOPP
LF
bison: BEFEHL
bison: INPUT


START



4. Schwache Integration in ein C++-Programm

In einem ersten Schritt integrieren wir den flex-bison-Parser in ein einfaches C++-Usage-Programm, in dem zwar schon ein Objekt b1 vom Typ world eingeführt wird, aber die Parserfunktionen sind mit diesem Objekt noch nicht direkt verbunden. An dieser Stelle wurden die Definitionen aus VL4 zunächst unverändert übernommen (ausgenommen die Funktion show_world(); diese ist jetzt 'private'.



I-PROGR3-flexbisonclass

flex + bison+class: Version 1





#ifndef WORLD_H
#define WORLD_H

#include <string>
#include <vector>

using namespace std;

/**
  * class world
  * 
  */

class world
{

/** Public methods: */
public:
  world();
    /**
      * User kann ein spiel starten
      */
    bool world_start_game(  );

    /**
      * Ein User beendet ein laufendes Spiel
      */
    bool world_stopp_game(  );

    /**
      * Ein User nimmt eine aktion vor
      */
    bool world_do_action( );



/** Private methods: */
private:
    /**
      * 
      */
    void show_world(  );

  /**
    * initAttributes sets all world attributes to its default 		               value
    * make sure to call this method within your class constructor
    */
 void initAttributes();


/**Attributes: */

private:
    /**
      * Alphabetische Kennzeichnung des Bretts
      */
    string world_alpha_leiste;
  
    string world_board_line;

    /**
      * 
      */

    struct board_cell {
      int color;
      char content;
    };
    vector<struct board_cell> board;

};

#endif // WORLD_H


/***********************+
*
* world.cpp
*
*************************/

#include "world.h"
#include <iostream>

using namespace std;

world::world(){
   initAttributes( );
 }

bool world::world_start_game(  )
{

  show_world(  );

}

bool world::world_stopp_game(  )
{
  cout << "THUESS! DAS SPIEL ENDET HIER.... " << endl;
}


bool world::world_do_action(  )
{

}



void world::show_world(  )
{ 
   const int white = 0;
   const int black = 1;
 int i,j;



 cout << world_board_line << endl;
 for(i=7; i>-1; i--){
   cout << endl << i+1 << " ";
 for(j=1; j<9; j++){

   switch(board[j+i*8].color) {

   case white:  { cout << board[j+i*8].color << " "; break; }

   case black:  { if(board[j+i*8].content == '_') { cout << board[j+i*8].color << " ";} 
                  else { if(board[j+i*8].content == 'w') cout << "w " ; 
		  else  cout << "s "; }
                  break; 
                 }
   default: { cout << " show_board switch ERROR" << endl; break;}
   }//End-of-switch color
 }//End-of-for j
 }//End-of-for i
   cout << endl;
 cout << world_board_line << endl;
 cout << world_alpha_leiste << endl;


}


void world::initAttributes( )
{
   const int white = 0;
   const int black = 1;
   int i,j;

  world_alpha_leiste = "  a b c d e f g h";
  world_board_line  = "  ________________";
  board.resize(65);

// Eintragen der Farben white/ black

 for(i=7; i>0; i-=2){
 for(j=1; j<9; j+=2){
  board[j+i*8].color = white;
  board[j+1+i*8].color = black;
 }//End-of-for j
 }//End-of-for i

 for(i=6; i>-1; i-=2){
 for(j=1; j<9; j+=2){
  board[j+i*8].color = black;
  board[j+1+i*8].color = white;
 }//End-of-for j
 }//End-of-for i

 // Eintragen der Spielsteine

 for(i=1; i<25; i++){
   if (board[i].color == black) board[i].content = 'w';
 }

 for(i=25; i<41; i++){
   if (board[i].color == black) board[i].content = '_';
 }

 for(i=41; i<65; i++){
   if (board[i].color == black) board[i].content = 's';
 }

}




/***********************+
*
* world_usage.cpp
*
*************************/

#include <iostream>
#include "world.h"

using namespace std;

int main(){

  extern int yyparse();
  extern int yylex();

  world b1;


  cout << "DAS PROGRAMM BEGINNT" << endl;

 b1.world_start_game(  );


  yyparse();

  return(1);


}


gerd@kant:~/public_html/fh/I-PROGR3/I-PROGR3-TH/VL7/VL7-QUELLEN/VL7-vers1> bison -d checker1.y
gerd@kant:~/public_html/fh/I-PROGR3/I-PROGR3-TH/VL7/VL7-QUELLEN/VL7-vers1> flex checker2.l
gerd@kant:~/public_html/fh/I-PROGR3/I-PROGR3-TH/VL7/VL7-QUELLEN/VL7-vers1> g++ -o checker1 checker_usage.cpp world.cpp lex.yy.c checker1.tab.c -lfl
checker2.l:34:7: warning: no newline at end of file

gerd@kant:~/public_html/fh/I-PROGR3/I-PROGR3-TH/VL7/VL7-QUELLEN/VL7-vers1> ./checker1
DAS PROGRAMM BEGINNT
  ________________

8 0 s 0 s 0 s 0 s
7 s 0 s 0 s 0 s 0
6 0 s 0 s 0 s 0 s
5 1 0 1 0 1 0 1 0
4 0 1 0 1 0 1 0 1
3 w 0 w 0 w 0 w 0
2 0 w 0 w 0 w 0 w
1 w 0 w 0 w 0 w 0
  ________________
  a b c d e f g h

LF
bison: LF
bison: INPUT
a3:b4
FELD KOPF FELD LF
bison: DO_ACTION
bison: BEFEHL
bison: INPUT
end
endLF
bison: LF
bison: INPUT
start
START
bison: START
LF
bison: BEFEHL
bison: INPUT


START



5. Starke Integration in ein C++-Programm

Unter einer starken Integration in ein C++-Programm verstehen wir hier den Sachverhalt, dass die Ergebnisse des flex-bison-Parsers --im folgenden einfach 'Parser' genannt-- von den Methoden einer Klasse benutzt werden.

Dabei stellt sich als erstes die Frage, wie denn grundsätzlich das Zusammenspiel zwischen Parser und benutzender Klasse im Rahmen einer Usage Datei konzipiert werden kann.

Da man im Rahmen der C-Quelltexte von flex und bison keinen direkten Bezug zu einer Klasse einbringen kann, muss man die Frage so stellen, wie man die ausserhalb einer C++-Klasse definierten Funktionen yylex() und yyparse() mit einer beliebigen C++Klasse in Beziehung setzen kann. Wenn man den Weg mittels friend-Funktionen nicht gehen will, weil dies das Prinzip der Datenkapselung aufweichen würde, bietet sich zunächst nur der Weg eines Datenaustausches über Variablen an, d.h. die Ergebnisse des Parsers müssen in Variablen geschrieben werden, die dann von den Methoden der benutzenden Klasse gelesen werden können.

Zusätzlich muss man sich die Gesamtfunktion des Usage-Programmes unter Berücksichtigung des Parsers neu bedenken. Das nachfolgende Schaubild zeigt eine Möglichkeit:



I-PROGR3-flexbison+class

Mögliche Kooperation Parser und Klasse



Diesem Schaubild liegt die Annahme zugrunde, dass der Parser, wenn er aufgerufen wird, die standardmässige Ein- und Ausgabe besetzt. Dies bedeutet, dass die abwechselnden Züge, in denen die Spieler Zeichenketten eingeben, durch den Parser abgefragt werden müssen. Nach jeder Eingabe muss dann aber der Parser die Kontrolle wieder abgeben, damit andere Funktionen die Verarbeitung der Eingabeergebnisse weiterführen können. Die funktion yyparse() beendet ihre Arbeit, wenn Sie auf ein EOF (End-of-FILE) trifft. Eine einfache Möglichkeit, dies zu realisieren, zeigt das folgende Beispiel, das zunächst nur den wiederholten Aufruf von yyparse() in einer Schleife zeigt, einschliesslich einer durch die Eingabe gesteuerte Beendigung mit 'ende'. Der weitere Datenaustausch zwischen yyparse() und der Klasse ist noch nicht enthalten.


/***********************+
*
* checker2b.l
*
*************************/

%{
 #include <stdio.h>
 #include "checker2.tab.h"
%}

 char loop;

LF  \n
KOPF :
FORTSETZUNG ,
FELD [a-h][1-8]
START start
ENDE ende

%%
[ \t]
{START}   {  printf("START \n"); return(START); }
{ENDE}    { printf("ENDE \n "); loop='q'; return(ENDE); }
{FELD}    { printf("FELD "); return(FELD); }
{KOPF}    { printf("KOPF "); return(KOPF); }
{FORTSETZUNG}    { printf("FORTSETZUNG "); return(FORTSETZUNG); }
{LF}    { printf("LF \n"); return(LF); }

%%

     void
     yyerror (const char *s)  /* called by yyparse on error */
     {
       printf ("%s\n", s);
     }  


/***********************+
*
* checker2.y
*
*************************/

%{
 #include <stdio.h>
 #define EOF -1
 extern int yylex();
 extern void yyerror(const char *); 

%}

%token START ENDE FELD KOPF FORTSETZUNG LF


%%
input:    /* empty */
          | input line  { printf("bison: INPUT \n"); return(EOF); }
     ;

line : LF    { printf("bison: LF \n"); }
     | befehl LF      { printf("bison: BEFEHL \n"); }
;

befehl : START    { printf("bison: START \n"); }
       | ENDE     { printf("bison: STOPP \n"); }
       | zug      { printf("bison: DO_ACTION \n"); }
;

zug : FELD KOPF FELD        /* { printf("bison: EINFACHER ZUG"); } */
    | zug FORTSETZUNG FELD  /* { printf("bison: KOMPLEXER ZUG"); } */
;

%%



/***********************+
*
* checker1_usage2.cpp
*
*************************/

#include <iostream>

using namespace std;

int main(){

  extern int yyparse();
  extern int yylex();

  extern char loop;

  loop='a';



  while(loop != 'q' ){

    cout << "Ihre EINGABE :\n" ;

  yyparse();


  }

  cout << "Die while-Schleife wurde durch Veraenderung von loop beendet" << endl;

}

gerd@kant:~/public_html/fh/I-PROGR3/I-PROGR3-TH/VL7> bison -d checker2.y
gerd@kant:~/public_html/fh/I-PROGR3/I-PROGR3-TH/VL7> flex checker2b.l
gerd@kant:~/public_html/fh/I-PROGR3/I-PROGR3-TH/VL7> g++ -o checker1b checker1_usage2.cpp lex.yy.c checker2.tab.c -lfl
gerd@kant:~/public_html/fh/I-PROGR3/I-PROGR3-TH/VL7> ./checker1b
Ihre EINGABE :
start
START
bison: START
LF
bison: BEFEHL
bison: INPUT
Ihre EINGABE :
c1:b2
FELD KOPF FELD LF
bison: DO_ACTION
bison: BEFEHL
bison: INPUT
Ihre EINGABE :
b2:d4,b6
FELD KOPF FELD FORTSETZUNG FELD LF
bison: DO_ACTION
bison: BEFEHL
bison: INPUT
Ihre EINGABE :
ende
ENDE
 bison: STOPP
LF
bison: BEFEHL
bison: INPUT
Die while-Schleife wurde durch Veraenderung von loop beendet

Bem weiteren Vorgehen zeigt sich, dass es notwendig ist, die Bestandteile eines Feldes, nämlich die Bezeichnung der Spalte ('column') wie auch die Bezeichnung der Zeile ('row') für die Auswertung zugänglich zu machen. Dazu muss das bisherige Makro FELD ersetzt werden durch die beiden Makros COL und ROW.

Zusätzlich zu dieser Unterscheidung von Spalten und Zeilen müssen aber auch die Werte der einzelnen Spalten und Zeilen von flex an bison übermittelt werden. Dies geschieht dadurch, dass man in flex die Zahlenwerte aus den Spalten und Zeilen in die Variable 'yylval' schreibt; diese wird von bison gelesen. In bison werden dann die von yylval empfangenen Werte in die Positionsvariablen $1, $2 etc. gespeichert. Diese Variablen $1, $2 usw. kann man abfragen und einen Zähler für die Anzahl der Felder in einem Zug mitführen..


/***********************+
*
* checker4.l
*
*************************/

%{
 #include <stdio.h>
 #include "checker4.tab.h"
%}

 extern int col2num(char);
 char loop;
 char c;

LF  \n
KOPF :
FORTSETZUNG ,
COL [a-h]
ROW [1-8]
START start
ENDE ende

%%
[ \t]
{START}   {  printf("START \n"); return(START); }
{ENDE}    { printf("ENDE \n "); loop='q'; return(ENDE); }
{COL}    { printf("COL "); c=yytext[0]; yylval=col2num(c); return(COL); }
{ROW}    { printf("ROW ");  yylval=atoi(yytext); return(ROW); }
{KOPF}    { printf("KOPF "); return(KOPF); }
{FORTSETZUNG}    { printf("FORTSETZUNG "); return(FORTSETZUNG); }
{LF}    { printf("LF \n"); return(LF); }

%%

     void
     yyerror (const char *s)  /* called by yyparse on error */
     {
       printf ("%s\n", s);
     }  

  int col2num(char c){

  return(c-96);
  }


/***********************+
*
* checker4.y
*
*************************/

%{
 #include <stdio.h>
 #define EOF -1
 extern int yylex();
 extern void yyerror(const char *); 

 int anz_felder = 0;

%}

%token START ENDE COL ROW KOPF FORTSETZUNG LF


%%
input:    /* empty */
          | input line  { printf("bison: INPUT mit %d -vielen FELDERN \n", anz_felder); anz_felder=0; return(EOF); }
     ;

line : LF    { printf("bison: LF \n"); }
     | befehl LF      { printf("bison: BEFEHL \n"); }
     ;

befehl : START    { printf("bison: START \n"); }
       | ENDE     { printf("bison: STOPP \n"); }
       | zug      { printf("bison: DO_ACTION \n"); }
       ;

zug : feld KOPF feld        /* { printf("bison: EINFACHER ZUG"); } */
    | zug FORTSETZUNG feld  /* { printf("bison: KOMPLEXER ZUG"); } */
    ;

feld : COL ROW       { printf("bison: COL=%d mit ROW=%d \n",$1,$2); anz_felder++; } 
      ;   

%%


gerd@kant:~/public_html/fh/I-PROGR3/I-PROGR3-TH/VL7/VL7-QUELLEN> bison -d checker4.y
gerd@kant:~/public_html/fh/I-PROGR3/I-PROGR3-TH/VL7/VL7-QUELLEN> flex checker4.l
gerd@kant:~/public_html/fh/I-PROGR3/I-PROGR3-TH/VL7/VL7-QUELLEN> g++ -o checker4 checker1_usage4.cpp lex.yy.c checker4.tab.c -lfl
gerd@kant:~/public_html/fh/I-PROGR3/I-PROGR3-TH/VL7/VL7-QUELLEN> ./checker4
Ihre EINGABE :
start
START
bison: START
LF
bison: BEFEHL
bison: INPUT mit 0 -vielen FELDERN
Ihre EINGABE :
c1:b2
COL ROW bison: COL=3 mit ROW=1
KOPF COL ROW bison: COL=2 mit ROW=2
LF
bison: DO_ACTION
bison: BEFEHL
bison: INPUT mit 2 -vielen FELDERN
Ihre EINGABE :
b2:d4,b6
COL ROW bison: COL=2 mit ROW=2
KOPF COL ROW bison: COL=4 mit ROW=4
FORTSETZUNG COL ROW bison: COL=2 mit ROW=6
LF
bison: DO_ACTION
bison: BEFEHL
bison: INPUT mit 3 -vielen FELDERN
Ihre EINGABE :
ende
ENDE
 bison: STOPP
LF
bison: BEFEHL
bison: INPUT mit 0 -vielen FELDERN
Die while-Schleife wurde durch Veraenderung von loop beendet

Für die Übergabe der Werte vom Parser zum Usage Programm und von da zur Klasse bieten sich mehrere Möglichkeiten an. Im folgenden wird eine Struktur 'feldwert' definiert, mit der dann ein Array gebildet wird, der genügend viele Elemente enthält, um einen maximalen Zug speichern zu können. Dieser Array genannt 'zugwerte' lässt sich von yyparse() mit Werten füllen und dann vom Usage Programm aus abfragen.


/***********************+
*
* checker.h
*
*************************/

 struct feldwert {
   int col;
   int row;
 };


/***********************+
*
* checker6.y
*
*************************/

%{
 #include <stdio.h>
 #include "checker.h"
 #define EOF -1
#define MAX_ZUG 13    /* Es gibt hoechstens 12 gegnerische Steine zum Ueberspringen */
 extern int yylex();
 extern void yyerror(const char *); 

 int anz_felder = 0;
 int anz_felder_last;
  
 struct feldwert zugwerte[MAX_ZUG];

%}

%token START ENDE COL ROW KOPF FORTSETZUNG LF


%%
input:    /* empty */
          | input line  { printf("bison: INPUT mit %d -vielen FELDERN \n", anz_felder);anz_felder_last=anz_felder;  anz_felder=0; return(EOF); }
     ;

line : LF    { printf("bison: LF \n"); }
     | befehl LF      { printf("bison: BEFEHL \n"); }
     ;

befehl : START    { printf("bison: START \n"); }
       | ENDE     { printf("bison: STOPP \n"); }
       | zug      { printf("bison: DO_ACTION \n"); }
       ;

zug : feld KOPF feld        /* { printf("bison: EINFACHER ZUG"); } */
    | zug FORTSETZUNG feld  /* { printf("bison: KOMPLEXER ZUG"); } */
    ;

feld : COL ROW       { printf("bison: COL=%d mit ROW=%d \n",$1,$2); zugwerte[anz_felder].col=$1; zugwerte[anz_felder].row=$2;  anz_felder++;} 
      ;   

%%



/***********************+
*
* checker1_usage6.cpp
*
*************************/

#include <iostream>
#include "checker.h"

using namespace std;

int main(){

  extern int yyparse();
  extern int yylex();
  extern char loop;

  extern int anz_felder;
  extern int anz_felder_last;

  extern struct feldwert zugwerte[];

  int i;

  loop='a';



  while(loop != 'q' ){

    cout << "Ihre EINGABE :\n" ;

  yyparse();

  cout << "ANZAHL FELDER =  " << anz_felder_last << endl;

  for(i=0; i< anz_felder_last; i++){

    cout << zugwerte[i].col <<  zugwerte[i].row << endl;

  }//End-of-for i

  }//End-of-while loop

  cout << "Die while-Schleife wurde durch Veraenderung von loop beendet" << endl;

}

gerd@kant:~/public_html/fh/I-PROGR3/I-PROGR3-TH/VL7/VL7-QUELLEN> bison -d checker6.y
gerd@kant:~/public_html/fh/I-PROGR3/I-PROGR3-TH/VL7/VL7-QUELLEN> flex checker6.l
gerd@kant:~/public_html/fh/I-PROGR3/I-PROGR3-TH/VL7/VL7-QUELLEN> g++ -o checker6 checker1_usage6.cpp lex.yy.c checker6.tab.c -lfl

gerd@kant:~/public_html/fh/I-PROGR3/I-PROGR3-TH/VL7/VL7-QUELLEN> ./checker6
Ihre EINGABE :
start
START
bison: START
LF
bison: BEFEHL
bison: INPUT mit 0 -vielen FELDERN
ANZAHL FELDER =  0
Ihre EINGABE :
c1:b2
COL ROW bison: COL=3 mit ROW=1
KOPF COL ROW bison: COL=2 mit ROW=2
LF
bison: DO_ACTION
bison: BEFEHL
bison: INPUT mit 2 -vielen FELDERN
ANZAHL FELDER =  2
31
22
Ihre EINGABE :
b2:d4,f6
COL ROW bison: COL=2 mit ROW=2
KOPF COL ROW bison: COL=4 mit ROW=4
FORTSETZUNG COL ROW bison: COL=6 mit ROW=6
LF
bison: DO_ACTION
bison: BEFEHL
bison: INPUT mit 3 -vielen FELDERN
ANZAHL FELDER =  3
22
44
66
Ihre EINGABE :
ende
ENDE
 bison: STOPP
LF
bison: BEFEHL
bison: INPUT mit 0 -vielen FELDERN
ANZAHL FELDER =  0
Die while-Schleife wurde durch Veraenderung von loop beendet
  

Da die Arbeit mit den vielen Dateien langsam aufwendig wird, lohnt es sich, ab jetzt das Werkzeug make zu benutzen. make liest ein Makefile und verwaltet entsprechend diesem Makefile eine Menge von Dateien. Unter anderem berücksichtigt make die Abhängigkeiten zwischen den Dateien sowie die Tatsache, ob seit dem letzten Aufruf bei den Dateien eine Veränderung stattgefunden hat. Im folgenden sei beispielhaft ein kleines Makefile vorgestellt.


#############################################################################
# Makefile for building: checker8
#############################################################################

####### Compiler, tools and options

CC       = gcc
CXX      = g++
LEX      = flex
YACC     = bison
CFLAGS   = -pipe -O2 -march=i586 -mcpu=i686 -fmessage-length=0 -fPIC -DNO_DEBUG -Wall -W -g  
CXXFLAGS = -pipe -O2 -march=i586 -mcpu=i686 -fmessage-length=0 -fPIC -DNO_DEBUG -Wall -W -g  
LEXFLAGS = 
YACCFLAGS= -d
LIBS     =  -lm -lfl

####### Output directory

OBJECTS_DIR = ./

####### Files

HEADERS = world.h \
		checker8.tab.h \
		checker.h
C-SOURCES = checker1_usage8.cpp \
		world.cpp \
		lex.yy.c \
		checker8.tab.c
F-SOURCES = checker8.l
B-SOURCES = checker8.y 
OBJECTS = checker1_usage8.o \
		world.o \
		lex.yy.o \
		checker8.tab.o
TARGET   = checker8

############## RULES FOR GENERATION

$(TARGET): $(OBJECTS) 
	g++  -o $(TARGET) $(OBJECTS) $(LIBS)

checker8.tab.h: checker8.y
	bison $(YACCFLAGS) checker8.y

checker8.tab.c: checker8.y checker.h
	bison $(YACCFLAGS) checker8.y

lex.yy.c: checker8.l checker8.tab.h
	flex checker8.l

checker1_usage8.o: checker1_usage8.cpp world.h checker.h
	g++ -c  checker1_usage8.cpp

lex.yy.o: lex.yy.c 
	g++ -c  lex.yy.c

checker8.tab.o: checker8.tab.c checker.h
	g++ -c  checker8.tab.c

world.o: world.cpp world.h
	g++ -c  world.cpp

clean:
	rm $(TARGET) $(OBJECTS) 

Zusäzlich zu dem bisherigen Datenaustausch von Spalten- und Zeilenwerten werden hier Ereignis-IDs eingeführt, die die Art der unterschiedlichen Benutzereingaben kodieren, z.B. '1' für 'start', '2' für 'ende' und '3' für einen Zug. Diese Ereignis-IDs werden in einem Zwischenspeicher zwischengespeichert. Das Usage-Programm kann diese Ereignis-IDs dazu benutzen, entsprechende Funktionen der Klasse world aufzurufen. Hier den Anfang der Realisierung dieser Idee.


/***********************+
*
* checker8.l
*
*************************/

%{
 #include <stdio.h>
 #include "checker8.tab.h"
%}

 extern int col2num(char);
 char loop;
 char c;

LF  \n
KOPF :
FORTSETZUNG ,
COL [a-h]
ROW [1-8]
START start
ENDE ende

%%
[ \t]
{START}   {  printf("START \n"); return(START); }
{ENDE}    { printf("ENDE \n "); loop='q'; return(ENDE); }
{COL}    { printf("COL "); c=yytext[0]; yylval=col2num(c); return(COL); }
{ROW}    { printf("ROW ");  yylval=atoi(yytext); return(ROW); }
{KOPF}    { printf("KOPF "); return(KOPF); }
{FORTSETZUNG}    { printf("FORTSETZUNG "); return(FORTSETZUNG); }
{LF}    { printf("LF \n"); return(LF); }

%%

     void
     yyerror (const char *s)  /* called by yyparse on error */
     {
       printf ("%s\n", s);
     }  

  int col2num(char c){

  return(c-96);
  }


/***********************+
*
* checker8.y
*
*************************/

%{
 #include <stdio.h>
 #include "checker.h"
 #define EOF -1
#define MAX_ZUG 13    /* Es gibt hoechstens 12 gegnerische Steine zum Ueberspringen */
 extern int yylex();
 extern void yyerror(const char *); 

 int anz_felder = 0;
 int anz_felder_last;
 struct feldwert zugwerte[MAX_ZUG];
 int action = 0;

%}

%token START ENDE COL ROW KOPF FORTSETZUNG LF


%%
input:    /* empty */
          | input line  { printf("bison: INPUT mit %d -vielen FELDERN \n", anz_felder); 
                          anz_felder_last=anz_felder;  anz_felder=0; return(EOF); }
     ;

line : LF    { printf("bison: LF \n"); }
     | befehl LF      { printf("bison: BEFEHL \n"); }
     ;

befehl : START    { printf("bison: START \n"); action = 1; }
       | ENDE     { printf("bison: STOPP \n"); action = 2;}
       | zug      { printf("bison: DO_ACTION \n"); action = 3; }
       ;

zug : feld KOPF feld        /* { printf("bison: EINFACHER ZUG"); } */
    | zug FORTSETZUNG feld  /* { printf("bison: KOMPLEXER ZUG"); } */
    ;

feld : COL ROW       { printf("bison: COL=%d mit ROW=%d \n",$1,$2); 
                      zugwerte[anz_felder].col=$1; zugwerte[anz_felder].row=$2;  
                      anz_felder++;} 
      ;   

%%



/***********************+
*
* checker1_usage8.cpp
*
*************************/

#include <iostream>
#include "checker.h"
#include "world.h"

using namespace std;

int main(){

extern int yyparse();
extern int yylex();
  extern char loop;

  extern int anz_felder;
  extern int anz_felder_last;        // Anzahl Felder des zuletzt ausgefuehrten Zuges
  extern int action;                // 1 = start, 2 = ende, 3 = zug

  extern struct feldwert zugwerte[]; //Alle Felder eines Zuges mit COL und ROW

  int i;

  world w1;

  loop='a';

  cout << "Das SPIEL BEGINNT" << endl;
  cout << "Moegliche EINGABEN sind:" << endl;
  cout << "------------------------------------------------" << endl;
  cout << "start" << endl;
  cout << "FELD:FELD[,FELD]*" << endl;
  cout << "ende" << endl;
  cout << "------------------------------------------------" << endl << endl;

  while(loop != 'q' ){

    cout << "Ihre EINGABE :" << endl;

  yyparse();

  switch(action){

  case 1: { cout << "case 'start' " << endl; 

           w1.world_start_game(  );

           break; 
           }//End-of case 1
 


  case 2:  { cout << "case 'ende' " << endl;
           
            w1.world_stopp_game(  );

             break; 
            }//End-of-case 2

  case 3: { cout << "case 'zug werte' " << endl;

            cout << "ANZAHL FELDER =  " << anz_felder_last << endl;

             for(i=0; i< anz_felder_last; i++){

             cout << zugwerte[i].col <<  zugwerte[i].row << endl;
            
            }//End-of-for i
            break; 
            }//End-of-case 3 

  default:  { cout << "case ERROR " << endl; break; } 

  }//End-of-switch action
 

  }//End-of-while loop

  cout << "Die while-Schleife wurde durch Veraenderung von loop beendet" << endl;

}

 gerd@kant:~/public_html/fh/I-PROGR3/I-PROGR3-TH/VL7/VL7-QUELLEN/VL7-vers8> make
g++ -c  checker1_usage8.cpp
g++ -c  world.cpp
g++ -c  lex.yy.c
g++ -c  checker8.tab.c
g++  -o checker8 checker1_usage8.o world.o lex.yy.o checker8.tab.o -lm -lfl
gerd@kant:~/public_html/fh/I-PROGR3/I-PROGR3-TH/VL7/VL7-QUELLEN/VL7-vers8> ./checker8
Das SPIEL BEGINNT
Moegliche EINGABEN sind:
------------------------------------------------
start
FELD:FELD[,FELD]*
ende
------------------------------------------------

Ihre EINGABE :
start
START
bison: START
LF
bison: BEFEHL
bison: INPUT mit 0 -vielen FELDERN
case 'start'
  ________________

8 0 s 0 s 0 s 0 s
7 s 0 s 0 s 0 s 0
6 0 s 0 s 0 s 0 s
5 1 0 1 0 1 0 1 0
4 0 1 0 1 0 1 0 1
3 w 0 w 0 w 0 w 0
2 0 w 0 w 0 w 0 w
1 w 0 w 0 w 0 w 0
  ________________
  a b c d e f g h
Ihre EINGABE :
a3:b4
COL ROW bison: COL=1 mit ROW=3
KOPF COL ROW bison: COL=2 mit ROW=4
LF
bison: DO_ACTION
bison: BEFEHL
bison: INPUT mit 2 -vielen FELDERN
case 'zug werte'
ANZAHL FELDER =  2
13
24
Ihre EINGABE :
ende
ENDE
 bison: STOPP
LF
bison: BEFEHL
bison: INPUT mit 0 -vielen FELDERN
case 'ende'
THUESS! DAS SPIEL ENDET HIER....
Die while-Schleife wurde durch Veraenderung von loop beendet
 

START



6. Übungen

In den Übungsstunden


START