/**
 * @file   toy_interpreter.c
 *  @brief  簡易インタプリタ サンプル.
 *  @author tenk*
 *  @note
 *      if,else,while文, print文, {複文}
 *      // コメントあり
 *      a-zの26個の1文字変数アリ.
 *      演算順位
 *          1   数値 単項- 単項+ 単項! (式)
 *          2   * / %
 *          3   + -
 *          4   > >= < <=
 *          5   == !=
 *  - Public Domain Software
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

// ===========================================================================

// どうも pcc(100306) では BSSに配置される変数の扱いがバグってる?ようなので、
// 強制的に初期値を設定しておく.

static const char *pCh_             = 0;    ///< 解析中の文字列へのポインタ.
static unsigned   tok_              = 0;    ///< トークンの種類.
static int        tokVal_           = 0;    ///< トークンが数値だった時の値.
static int        tokStrFlg_        = 0;    ///< 文字列か否か.
static char       tokName_[260]     = {0};  ///< 名前だったときの名前文字列.
static char       tokStr_[16*1024]  = {0};  ///< "文字列"だったときの文字列.
static int        variNo_           = -1;   ///< 変数だったときの番号.
static int        vari_[26]         = {0};  ///< 変数テーブル.


// ---------------------------------------------------------------------
#if !(defined __cplusplus) && !(defined inline) && defined _WIN32
#define inline  __inline
#endif

#ifdef COINS    // tolowerを使った箇所で')'がらみのエラーがでる..ので回避.
#define tolower(c)      (((c) >= 'A' && (c) <= 'Z') ? (c)+'a'-'A' : (c))
#endif

/// 2つの半角キャラを、上位バイト下位バイトに持つ値を返す.
#define CC(a,b)     (((a)<<8) | (b))

static int expr(void);


/// 簡易インタプリタ言語の初期化.
void Interpreter_init()
{
    int     n;
    pCh_        = 0;
    tok_        = 0;
    tokVal_     = 0;
    tokStrFlg_  = 0;
    variNo_     = -1;
    tokName_[0] = 0;
    tokStr_[0]  = 0;
    for (n = 0; n < 26; ++n)
        vari_[n] = 0;
}


/// エラーメッセージを表示して、exit.
static void exitputs(const char *msg)
{
    printf("%s", msg);
    exit(1);
}


/// 1文字取得.
static inline int getCh(void)
{
    int ch = *pCh_;
    if (ch)
        ++pCh_;     // EOF対策で ch == 0 のときはポインタを進めない.
    return ch;
}


/** 1語取得.
 * tok_は '+' '-' '/' '*' '(' ')' '!' '!=' '=' '==' '>' '>=' '<' '<='
 *        '0' 'V' 'i' 'e' 'w' 'p' '"' のいずれか.
 */
static void getTok(void)
{
    int i = 0;
    int ch;
    do {                        // 空白はスキップして1文字取得.
        ch = getCh();
        if (ch == '/' && *pCh_ == '/') {  // "//"があればコメント扱い.
            do {
                ch = getCh();
            } while (ch != '\n' && ch != '\0');
        }
    } while (0 < ch && ch <= 0x20);

    if (isdigit(ch)) {          // 数値. 10進数, 0xの16進数, 0で始まる8進数.
        tokVal_ = strtoul(pCh_-1, (char**)&pCh_, 0);
        tok_    = '0';          // 数値を表す値として'0'を用いる.

    } else if (isalpha(ch)) {   // アルファベットのとき.
        do {
            if (i < (int)sizeof(tokName_)-1)
                tokName_[i++] = tolower( ch );
            ch = getCh();
        } while (isalnum(ch));
        --pCh_;
        tokName_[i] = 0;
        variNo_     = -1;
        if      (i == 1) {variNo_=*tokName_-'a'; tok_ = 'V'; }  // 英1字は変数.
        else if (strcmp(tokName_, "if"   ) == 0) tok_ = 'i';    // if
        else if (strcmp(tokName_, "else" ) == 0) tok_ = 'e';    // else
        else if (strcmp(tokName_, "while") == 0) tok_ = 'w';    // while
        else if (strcmp(tokName_, "print") == 0) tok_ = 'p';    // print
        else    printf("unkown name `%s'\n", tokName_);

    } else if (ch == '"') {     // "文字列"
        while ((ch = getCh()) != '"') {
            if (ch == 0)
                exitputs("bad EOF\n");
            if (i < (int)(sizeof tokStr_)-1)
                tokStr_[i++] = ch;
        }
        tokStr_[i] = 0;
        tok_ = '"';
    } else {    // 記号
        tok_ = ch;
        switch(ch) {
        case '/':   case '*':   case '%':
        case '+':   case '-':
        case '(':   case ')':
        case '{':   case '}':
        case ';':   case ',':
        case '\0':
            break;

        case '=': if (*pCh_ == '=') { ++pCh_; tok_ = CC('=','='); } break;
        case '!': if (*pCh_ == '=') { ++pCh_; tok_ = CC('!','='); } break;
        case '>': if (*pCh_ == '=') { ++pCh_; tok_ = CC('>','='); } break;
        case '<': if (*pCh_ == '=') { ++pCh_; tok_ = CC('<','='); } break;
        default:
            printf("想定していない文字'%c'があらわれた\n", ch);
        }
    }
}


/// 数値, -式, +式, (式)  単項の処理.
static int exprUnary(void)
{
    int      l = 0;
    tokStrFlg_ = 0;
    getTok();
    if (tok_ == '0') {              // 数値の処理.
        l = tokVal_;
        getTok();
    } else if (tok_ ==  'V') {      // 変数の処理.
        l = vari_[variNo_];
        getTok();
    } else if (tok_ == '"') {       // 文字列の処理.
        tokStrFlg_ = 1;
        getTok();
    } else if (tok_ == '-') {       // -負号.
        l = -exprUnary();
    } else if (tok_ == '+') {       // +
        l =  exprUnary();
    } else if (tok_ == '!') {       // 否定.
        l =  !exprUnary();
    } else if (tok_ == '(') {       // ( 式 ) の処理.
        l = expr();
        if (tok_ == ')')
            getTok();
        else
            exitputs("There is no ')'.\n");
    } else {
        exitputs("The expression is illegal.\n");
    }
    return l;
}


/// 掛け算、割り算、余り算の処理.
static int exprMulDivMod(void)
{
    int l = exprUnary();
    for (; ;) {
        if (tok_ == '*') {
            l = l * exprUnary();
        } else if (tok_ == '/') {
            int n = exprUnary();
            if (n == 0)
                exitputs("It divided by 0.\n");
            else
                l = l / n;
        } else if (tok_ == '%') {
            int n = exprUnary();
            if (n == 0)
                exitputs("It divided by 0.\n");
            else
                l = l % n;
        } else {
            break;
        }
    }
    return l;
}


/// 足し算, 引き算の処理.
static int exprAddSub(void)
{
    int l = exprMulDivMod();
    for (; ;) {
        if (tok_ == '+') {
            l = l + exprMulDivMod();
        } else if (tok_ == '-') {
            l = l - exprMulDivMod();
        } else {
            break;
        }
    }
    return l;
}


/// < <= > >= の大小比較演算の処理.
static int exprCmpLtGt(void)
{
    int l = exprAddSub();
    for (; ;) {
        if (tok_ == '<') {
            l = l <  exprAddSub();
        } else if (tok_ == CC('<','=')) {
            l = l <= exprAddSub();
        } else if (tok_ == '>') {
            l = l >  exprAddSub();
        } else if (tok_ == CC('>','=')) {
            l = l >= exprAddSub();
        } else {
            break;
        }
    }
    return l;
}


/// == != の等値比較演算の処理.
static int expr(void)
{
    int l = exprCmpLtGt();
    for (; ;) {
        if (tok_ == CC('=','=')) {
            l = l == exprCmpLtGt();
        } else if (tok_ == CC('!','=')) {
            l = l != exprCmpLtGt();
        } else {
            break;
        }
    }
    return l;
}


/// if,else,while,print,{複文} 等の処理. eflg=1なら実行,0ならスキップ.
void statement(int eflg)
{
    int         n;
    if (tok_ == ';') {              // 空行.
        getTok();
    } else if (tok_ == 'i') {       // if の処理.
        n = (expr() != 0);
        statement(eflg & n);
        if (tok_ == 'e')            // else の処理.
            statement(eflg & !n);
    } else if (tok_ == 'w') {       // while の処理.
        const char *wp = pCh_;
        do {
            pCh_ = wp;
            n = (expr() != 0);
            statement(eflg & n);
        } while (eflg & n);
    } else if (tok_ == '{') {       // { 複文 } の処理.
        getTok();
        do {
            statement(eflg);
        } while (tok_ != '}');
        getTok();
    } else if (tok_ == 'V') {       // 変数への代入処理.
        int e;
        n = variNo_;
        getTok();
        if (tok_ != '=')
            exitputs("Syntax Error.\n");
        e = expr();
        if (eflg)
            vari_[(int)n] = e;
        if (tok_ == ';')
            getTok();
    } else if (tok_ == 'p') {       // print文.
        do {
            n = expr();
            if (eflg) {
                if (tokStrFlg_)
                    printf("%s", tokStr_);
                else
                    printf("%d", n);
            }
        } while (tok_ == ',');
        if (eflg)
            printf("\n");
        if (tok_ == ';')
            getTok();
    } else if (tok_ == 0) {         // EOF のとき.
        ;
    } else {
        exitputs("Syntax Error\n");
    }
}


/// 引数の文字列を計算した結果を返す.
int Interpreter_run(const char *p)
{
    pCh_  = p;
    getTok();
    while (tok_ != 0) { // eof が現れるまで実行.
        statement(1);
    }
    return 1;
}


// ===========================================================================
// 時間計測関係
#ifndef PERFCNT_TICK_T
#ifdef _WIN32
#include <windows.h>
typedef unsigned __int64    PERFCNT_TICK_T;
inline PERFCNT_TICK_T       PERFCNT_getTick()    { PERFCNT_TICK_T c; QueryPerformanceCounter((LARGE_INTEGER*)&c); return c; }
inline PERFCNT_TICK_T       PERFCNT_tickPerSec() { static PERFCNT_TICK_T s = 0; if (!s) QueryPerformanceFrequency((LARGE_INTEGER*)&s); return s;  }
#elif defined LINUX
#include <sys/resource.h>
typedef  unsigned long long PERFCNT_TICK_T;
inline PERFCNT_TICK_T       PERFCNT_getTick()   { struct rusage t; getrusage(RUSAGE_SELF, &t); return t.ru_utime.tv_sec * 1000000ULL + t.ru_utime.tv_usec; }
#define                     PERFCNT_tickPerSec()    1000000ULL
#else
#include <time.h>
typedef clock_t                 PERFCNT_TICK_T;
#define PERFCNT_getTick()       clock()
#define PERFCNT_tickPerSec()    CLOCKS_PER_SEC
#endif
#endif


// ===========================================================================

/// テキストファイル読みこみ.
char *textfile_load(const char *name, char *buf, unsigned bufSz)
{
    int l;
    FILE *fp = name ? fopen(name,"rb") : stdin; // ファイルオープン.
    if (fp == 0)
        return 0;
    l  = fread(buf, 1, bufSz-1, fp);            // ファイル読み込み.
    if (ferror(fp) || l < 0) {
        printf("%s\n", strerror( errno));
        return 0;
    }
    buf[l] = 0;
    if (name)
        fclose(fp);
    return buf;
}


/// プログラムのエントリー.
int main(int argc, char *argv[])
{
    // pccでの不具合回避で static化&サイズを16KBに下げる.
    static char     buf[0x4000];
    PERFCNT_TICK_T  tim;
    if (textfile_load((argc >= 2) ? argv[1] : 0, buf, sizeof buf) == 0) {
        if (argv[1])
            printf("File '%s' not loaded.\n", argv[1]);
        return 1;
    }
    tim = PERFCNT_getTick();
    Interpreter_init();
    Interpreter_run(buf);
    tim = PERFCNT_getTick() - tim;
    printf("\n%8.3g sec.\n", (double)tim / PERFCNT_tickPerSec() );
    return 0;
}