// Klammernprüfung, rekursiv
//
// Aufruf: klammer [infile ...]
// 
// Klaus Kusche, 2010

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

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;
    switch (c) {
      // 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
      case '(':
        if (!expect(')')) return false;
        break;
      case '[':
        if (!expect(']')) return false;
        break;
      case '{':
        if (!expect('}')) return false;
        break;
      case ')':
      case ']':
      case '}':
        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;
        }
        break;
      case '\n':
        ++line; col = 0;
        break;
      default:
        break;
    }
  }
  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);
}
