// Klammernprüfung, rekursiv
// Alternative Lösung mit strchr statt switch, thx Rainer
//
// Aufruf: klammer [infile ...]
// 
// Klaus Kusche, 2010

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>

// Liste der öffnenden und der schließenden Klammern
// (müssen paarweise zusammenpassen)
const char opening[] = "([{";
const char closing[] = ")]}";

const char *progName;        // für Fehlermeldungen in der Funktion

// global, weil für alle rekursiven Aufrufe gemeinsam
FILE *f;                     // der gerade bearbeitete File
const char *fName;           // sein Name
int line = 1, col = 0;       // Zeile und Spalte für Fehlermeldungen

bool expect(char close);

// Wir hatten eine öffnende Klammer
// und erwarten die dazugehörige Klammer 'close':
// Verarbeite den Input bis einschließlich 'close'
// Mach einen rekursiven Aufruf, wenn noch eine Klammer aufgeht
// Wenn close gleich '\0' ist:
// Äußerster Aufruf, keine Klammer offen
// Verarbeite den Input bis EOF
// Ergebnis: true wenn alles passt, false bei Fehler
bool expect(char close)
{
  int c;                     // Aktuelles Zeichen (int wegen EOF!)

  while ((c = fgetc(f)) != EOF) {
    ++col;
    char *p = strchr(opening, c); // Pointer auf aktuelle Klammer auf in opening
    if (p != NULL) {  // c ist in opening, d.h. c ist eine Klammer auf
      // bei Klammer auf:
      // lies alles bis zur dazugehörigen Klammer zu in einem rek. Aufruf
      // wenn Fehler: Mit Fehler enden
      // wenn ok: Auf aktueller Klammern-Ebene schließende Klammer weitersuchen
      //
      // p - opening liefert den Index der Klammer in opening und closing
        if (!expect(closing[p - opening])) return false;
    } else if (strchr(closing, c) != NULL) {  // Klammer zu
        if (c == close) {
          // die erwartete Klammer zu ==> passt, fertig!
          return true;  
        } else if (close == '\0') {
          // es ist gar keine Klammer offen!
          printf("%s:%d:%d: Closing %c was never opened\n",
                 fName, line, col, c);
          return false;
        } else {
          // es geht die falsche Klammer zu!
          printf("%s:%d:%d: Expected %c, found %c\n",
                 fName, line, col, close, c);
          return false;
        }
    } else if (c == '\n') {
       ++line; col = 0;
    } else { // Andere Zeichen: Nichts zu tun
    }
  }
  if (ferror(f)) {
    fprintf(stderr, "%s: cannot read %s: %s\n",
            progName, fName, strerror(errno));
    return false;
  }
  if (close == '\0') {
    // es ist keine Klammer offen, erfolgreich fertig
    return true;
  } else {
    // File-Ende, ohne dass die gesuchte schließende Klammer gefunden wurde
    printf("%s:%d:%d: %c missing at end of file\n",
            fName, line, col, close);
    return false;
  }
}

int main(int argc, const char *argv[])
{
  progName = argv[0];

  if (argc == 1) {   // kein File angegeben!
    f = stdin;
    fName = "stdin";
    expect('\0');
  } else {
    for (int i = 1; i < argc; ++i) {
      fName = argv[i];
      f = fopen(fName, "r");
      if (f == NULL) {
        fprintf(stderr, "%s: cannot open %s for reading: %s\n",
                progName, fName, strerror(errno));
        exit(EXIT_FAILURE);
      } else {
        expect('\0');
        fclose(f);
      }
    }
  }

  exit(EXIT_SUCCESS);
}
