/*
再帰的下向き構文解析プログラム
（以下の文法に対する構文解析プログラム）
   Expr    -> CON
            | FID '(' Expr2 ')'
   Expr2   -> Expr Rest
   Rest    -> ',' Expr
            | ε

   -- 以下は終端記号: 字句解析部で処理
   EOL     -> '\n'                    -- End Of Line
   CON     -> '0' | '1' | ... | '9'   --  一桁の数のみ
   FID     -> '+' | '-' | '*' | '!'
 */

#include <stdio.h>
#include <stdlib.h>   /* exit()用 */
#include <ctype.h>    /* isdigit()用 */

/* 大域変数の宣言 */
int token;      /* 入力の先頭のトークンを表す */
int yylval;     /* tokenの属性 */

int column = 0; /* デバッグ用 */ 

/* 終端記号に対応するマクロの定義 */
#define CON 256
#define FID 257
#define EOL 258 /* End of Line -- $に相当 */ 

/* 簡易字句解析ルーチン */
int yylex(void) {   
    int c;

    if (token == EOL) { /* 前のトークンが改行だったら */
        column=0;
    }

    do {
        c = getchar ();
        column++;
    } while (c == ' ' || c == '\t');

    if (isdigit (c)) {
        yylval = c-'0'; /* 数字から数へ変換 */
        return CON;
    }

    if (c == '+' || c== '-' || c == '*' ||c =='!') {
        yylval = c;
        return FID;
    }

    if (c == '\n') {
        return EOL; /* 行末が $（入力の終わり）に対応する */
    }
    
    if (c == EOF) { /* ファイルの終 */
        exit(0); 
    }
    /* 上のどの条件にも合わなければ、文字をそのまま返す。*/
    return c;   /* '(', ')', ','など */
}

char* tokenName(int t) {
    switch (t) {
    case 256: return "CON";
    case 257: return "FID";
    case 258: return "EOL";
    default:  return "Unknown";
    }
}

/* token（終端記号）を消費して、次の tokenを読む */
void eat(int t) { 
    if (token ==  t) {
        token = yylex();
        return;
    } else {
        if (isprint(t)) {
            printf("eat: Character '%c' is expected at column %d ", t, column);
        } else {
            printf("eat: Token %s is expected at column %d ",
                   tokenName(t), column);
        }
        if (isprint(token)) {
            printf("instead of '%c'.\n", token);
        } else {
            printf("instead of %s.\n", tokenName(token));
        }
        exit(1);
    }
}

/* エラーメッセージの出力 */
void errMessage(char* place) {
    if (isprint(token)) {
        printf("%s: Unexpected token: '%c' at column %d.\n", place, token, column);
    } else {
        printf("%s: Unexpected token: %s at column %d.\n", 
               place, tokenName(token), column);
    }  
}
/* 関数プロトタイプ宣言 */
void Expr(void);
void Expr2(void);
void Rest(void);

/* 
   再帰的構文解析関数群
   文法の各非終端記号に対応する関数
 */
void Expr(void) {
    switch (token) {
    case CON:
        eat(CON); break;
    case FID:    
        eat(FID); eat('('); Expr2(); eat(')'); break;
    default:
        errMessage("Expr");
        exit(1);
        break;
    }
}

void Expr2(void) {
    if (token == CON || token == FID ) {
        Expr(); Rest();
    } else {
        errMessage("Expr2");
        exit(1);
    }
}

void Rest(void) {
    switch (token) {
    case ',':
        eat(','); Expr(); break;
    case ')':
        /* do nothing */ break;
    default:
        errMessage("Rest");
        exit(1);
        break;
    }
}

/* 各行の処理 */
void processLine(void) {
    Expr();
    if (token == EOL) { /* 入力がブロックしないように改行は特別扱い */
        printf("Correct!\n");   /* eat(EOL)の前に出力しておく */
    }
    eat(EOL);
}

/* main関数 */
int main(void) {
    printf("Ctrl-cで終了します。\n");
    token = yylex(); /* 最初のトークンを読む */
    while (1 /* 無限ループ */) {
        processLine();  /* 各行を処理する */
    }
}
