// ANALIZATOR LEKSYKALNY
//
// Wczytuje ze standardowego wejscia ciag znakow.
// Drukuje na standardowe wyjscie podzial tego ciagu na leksemy.


#include<stdio.h>
#include<stdlib.h>

#define max_dl_leksemu 100

typedef enum { FALSE = 0, TRUE = 1 }  Boolean;

typedef enum { STAN_POCZ   =  0,
               STAN_B      =  1,
               STAN_BE     =  2,
               STAN_E      =  3,
               STAN_EN     =  4,
               STAN_LICZ   =  5,
               STAN_CIACH  =  6,
               STAN_CGW    =  7,
               STAN_CGWGW  =  8,
               STAN_SEPAR  =  9,
               STAN_KLUCZ  = 10,
               STAN_OPER   = 11,
               STAN_KOMENT = 12,
               STAN_BLAD   = 13  }  Stany;

typedef enum { ZNAK_B      =  0,
               ZNAK_E      =  1,
               ZNAK_G      =  2,
               ZNAK_N      =  3,
               ZNAK_D      =  4,
               ZNAK_PL     =  5,
               ZNAK_GW     =  6,
               ZNAK_CYF    =  7,
               ZNAK_CIACH  =  8,
               ZNAK_SP     =  9,
               ZNAK_INNY   = 10  }  Znaki;

typedef enum { KLUCZ  = 0,
               OPER   = 1,
               LICZ   = 2,
               KOMENT = 3,
               SEPAR  = 4 }  Jednostki;

struct leksem {
  Jednostki jed;             // rodzaj leksemu
  int dlug;                  // dlugosc leksemu
                             //  ( 1 <= dlug <= max_dl_leksemu )
  char zaw[max_dl_leksemu];  // chary skladajace sie na leksem
};


void  blad (char s[], char ch) {
  // sygnalizacja bledu; drukuje napis  s  i char  ch
  // a nastepnie sie zatrzymuje
  printf ("!!! %s  '%c' !!!\n\n", s, ch);
  exit(1);
}


Znaki  kategoria (char ch) {
  // ta funkcja grupuje chary z wejscia w elementy typu  Znaki ;
  // to znaczy zapomina o roznicy miedzy literami malymi i duzymi,
  // wszystkim cyfrom przypisuje  ZNAK_CYF ,
  // nielegalnym znakom przypisuje  ZNAK_INNY
  if (ch == 'b' || ch == 'B')  return ZNAK_B;     else
  if (ch == 'e' || ch == 'E')  return ZNAK_E;     else
  if (ch == 'g' || ch == 'G')  return ZNAK_G;     else
  if (ch == 'n' || ch == 'N')  return ZNAK_N;     else
  if (ch == 'd' || ch == 'D')  return ZNAK_D;     else
  if (ch == '+')               return ZNAK_PL;    else
  if (ch == '*')               return ZNAK_GW;    else
  if (ch == '/')               return ZNAK_CIACH; else
  if (ch == ' ')               return ZNAK_SP;    else
  if ('0' <= ch && ch <= '9')  return ZNAK_CYF;
  else  return ZNAK_INNY;
}


Stany  automat[10][11]; 
  // -- funkcja przejscia automatu; indeksowana stanami
  // od STAN_POCZ do STAN_SEPAR oraz znakami od ZNAK_B do ZNAK_INNY

char  wejscie;
  // char przeczytany na zapas z wejscia


void  leks (struct leksem* lk) {
  // zasadnicza funkcja programu;
  // jej wywolanie powoduje wczytanie odpowiedniej liczby charow z
  // wejscia i zlozenie z nich leksemu (lub sygnalizacje bledu);
  // zakladamy, ze w momencie wywolania w zmiennej globalnej  wejscie
  // jest juz wczytany jeden char z wejscia;
  // po zakonczeniu dzialania tej funkcji w tej zmiennej jest juz
  // wczytany jeden char na zapas
  Stany  q, q1;
  Znaki  z;
  Boolean  koniec;
  q1 = STAN_POCZ; lk->dlug = 0; koniec = FALSE;
  do {
    z = kategoria(wejscie);
    q = q1; q1 = automat[q][z];
    if (q1 == STAN_BLAD)  { /* blad */ q1 = q; koniec = TRUE; }
    else
      if (q1 > STAN_SEPAR) { /* koniec leksemu */
        lk->zaw[lk->dlug] = wejscie;
          /* Uwaga: brak sygnalizacji przepelnienia
             tablicy  lk->zaw  */
        lk->dlug++; scanf("%c", &wejscie);
        koniec = TRUE;
      } else {
        lk->zaw[lk->dlug] = wejscie;
          /* Uwaga: brak sygnalizacji przepelnienia
             tablicy  lk->zaw  */
        lk->dlug++; scanf("%c", &wejscie);
      }
  }  while (! koniec);
    // po zakonczeniu powyzszej petli w  q1  jest stan, na ktorym
    // automat zakonczyl dzialanie; pozostaje sprawdzic, czy to jest
    // stan akceptujacy i co akceptujacy
  switch (q1) {
    case STAN_POCZ   : blad("Nieoczekiwany znak", wejscie); break;
    case STAN_B      : blad("Mialo byc 'beg' a jest", wejscie); break;
    case STAN_BE     : blad("Mialo byc 'beg' a jest", wejscie); break;
    case STAN_E      : blad("Mialo byc 'end' a jest", wejscie); break;
    case STAN_EN     : blad("Mialo byc 'end' a jest", wejscie); break;
    case STAN_CIACH  : blad("Mial byc komentarz a jest", wejscie); break;
    case STAN_CGW    : blad("Mial byc komentarz a jest", wejscie); break;
    case STAN_CGWGW  : blad("Mial byc komentarz a jest", wejscie); break;
    case STAN_LICZ   : lk->jed = LICZ; break;
    case STAN_SEPAR  : lk->jed = SEPAR; break;
    case STAN_KLUCZ  : lk->jed = KLUCZ; break;
    case STAN_OPER   : lk->jed = OPER; break;
    case STAN_KOMENT : lk->jed = KOMENT; break;
    case STAN_BLAD   : blad("Nieoczekiwany znak", wejscie); break;
  }
}


void  wydruk (struct leksem lk) {
  // wydruk pojedynczego leksemu
  int i;
  printf("LEKSEM:  ");
  switch (lk.jed) {
    case KLUCZ  : printf("klucz      '"); break;
    case OPER   : printf("operator   '"); break;
    case LICZ   : printf("liczba     '"); break;
    case KOMENT : printf("komentarz  '"); break;
    case SEPAR  : printf("separator  '"); break;
  }
  for (i=0; i<lk.dlug; i++)  printf("%c", lk.zaw[i]);
  printf("'\n");
}


int main () {
  //------------------------------------------------------
  // Inicjalizacja automatu:

  struct leksem  lk;

  for (Stany q = STAN_POCZ; q <= STAN_SEPAR; q++)
    for (Znaki z = ZNAK_B; z <= ZNAK_INNY; z++)
      automat[q][z] = STAN_BLAD;

  for (Stany q = STAN_CGW; q <= STAN_CGWGW; q++)
    for (Znaki z = ZNAK_B; z <= ZNAK_INNY; z++)
      automat[q][z] = STAN_CGW;

  automat[STAN_POCZ][ZNAK_B] = STAN_B;
  automat[STAN_POCZ][ZNAK_E] = STAN_E;
  automat[STAN_POCZ][ZNAK_PL] = STAN_OPER;
  automat[STAN_POCZ][ZNAK_GW] = STAN_OPER;
  automat[STAN_POCZ][ZNAK_CYF] = STAN_LICZ;
  automat[STAN_POCZ][ZNAK_CIACH] = STAN_CIACH;
  automat[STAN_POCZ][ZNAK_SP] = STAN_SEPAR;

  automat[STAN_B][ZNAK_E] = STAN_BE;
  automat[STAN_BE][ZNAK_G] = STAN_KLUCZ;
  automat[STAN_E][ZNAK_N] = STAN_EN;
  automat[STAN_EN][ZNAK_D] = STAN_KLUCZ;
  automat[STAN_LICZ][ZNAK_CYF] = STAN_LICZ;

  automat[STAN_CIACH][ZNAK_GW] = STAN_CGW;
  automat[STAN_CGW][ZNAK_GW] = STAN_CGWGW;
  automat[STAN_CGWGW][ZNAK_GW] = STAN_CGWGW;
  automat[STAN_CGWGW][ZNAK_CIACH] = STAN_KOMENT;

  automat[STAN_SEPAR][ZNAK_SP] = STAN_SEPAR;

  //------------------------------------------------------
  // Inne inicjalizacje:

  printf("\n"); scanf("%c", &wejscie); printf("\n"); 

  //------------------------------------------------------
  // Analiza leksykalna:

   while (wejscie != '\n')  { leks(&lk); wydruk(lk); }
   printf("\n");

  //------------------------------------------------------

   return 0;
}
