#include <iostream>
#include <string>
#include <sstream>
#include <cctype>
#include <map>
#include <fstream>

using namespace std;

// Wyrażenia //////////////////////////////////////////////////////

typedef map<string, int> Memory;

class Expression
{
  public:
    virtual int eval(Memory& m) = 0;
};

class Constant : public Expression
{
    int value;

  public:
    Constant(int v) : value(v) {}

    virtual int eval(Memory& m)
    {
      return value;
    }
};

class Unknown_operator { };

class Binary_operator : public Expression
{
    char symbol;
    Expression *left, *right;

  public:
    Binary_operator(char s, Expression* l, Expression* r)
      : symbol(s), left(l), right(r) {}

    virtual int eval(Memory& m)
    {
      switch (symbol)
      {
        case '*': return left->eval(m) * right->eval(m);
        case '+': return left->eval(m) + right->eval(m);
        case '-': return left->eval(m) - right->eval(m);
        case '/': return left->eval(m) / right->eval(m);
        case '%': return left->eval(m) % right->eval(m);
        default : throw Unknown_operator();
      }
    }
};

class Variable_not_found { };

class Variable : public Expression
{
    string name;

  public:
    Variable(string n) : name(n) { }
  
    virtual int eval(Memory& m)
    {
      Memory::iterator it = m.find(name);
      if (it == m.end())
        throw Variable_not_found();
      return it->second;
    }
};

// Język programowania /////////////////////////////////////////////

class Program
{
  public:
    virtual void eval(Memory& m) = 0;
};

class Read : public Program
{
    string var;

  public:
    Read(string v) : var(v) { }

    virtual void eval(Memory& m)
    {
      int n;
      cin >> n;
      m[var] = n;
    }
};

class Write : public Program
{
    string var;

  public:
    Write(string v) : var(v) { }

    virtual void eval(Memory& m)
    {
      Memory::iterator it = m.find(var);
      if (it == m.end())
        throw Variable_not_found();
      cout << m[var] << endl;
    }
};

class Assign : public Program
{
    string var;
    Expression* expr;

  public:
    Assign(string v, Expression* e) : var(v), expr(e) { }

    virtual void eval(Memory& m)
    {
      m[var] = expr->eval(m);
    }
};

class Skip : public Program
{
  public:
    virtual void eval(Memory& m) { }
};

class Composition : public Program
{
    Program* left, * right;

  public:
    Composition(Program* l, Program* r) : left(l), right(r) { }

    virtual void eval(Memory& m)
    {
      left->eval(m);
      right->eval(m);
    }
};

class If : public Program
{
    Program* branch_then, * branch_else;
    Expression* condition;

  public:
    If(Expression* c, Program* t, Program* e)
      : condition(c), branch_then(t), branch_else(e) { }

    virtual void eval(Memory& m)
    {
      if (condition->eval(m) != 0)
        branch_then->eval(m);
      else
        branch_else->eval(m);
    }
};

class While : public Program
{
    Program *body;
    Expression* condition;

  public:
    While(Expression* c, Program* b)
      : condition(c), body(b) { }

    virtual void eval(Memory& m)
    {
      if (condition->eval(m) != 0)
      {
        body->eval(m);
        this->eval(m);
      }
    }
};


// Parser ///////////////////////////////////////////////////////////

const char EOS = NULL;

class Not_parsed
{
  public:
    string reason;

    Not_parsed() {};

    Not_parsed(string s) : reason(s) { }
};

class Parser
{
    string input;    // analizowany tekst
    size_t position; // aktualnie "oglądany" znak

  public:

    Parser(string input);

    void skip_whitespace(); // pomija wszystkie białe znaki i przesuwa wskaźnik na pierwszy znak, który nie jest biały
    char look_ahead();      // pomija białe znaki i informuje jaki znak "oglądamy"

    Expression* parse_sum();        // parsuje "sumę"
    Expression* parse_mult();       // parsuje skłądnik
    Expression* parse_term();       // parsuje czynnik
    Expression* parse_Constant();   // parsuje liczbę
    Expression* parse_Variable();   // parsuje nazwę zeminnej
    Expression* parse_paren();      // parsuje "sumę" w nawiasie

    string parse_identifier();

    Program* parse_Program();
    Program* parse_block();
    Program* parse_instruction();
    Program* parse_Read();
    Program* parse_Write();
    Program* parse_If();
    Program* parse_While();
    Program* parse_Assign(string v);
};

Program* Parser::parse_Program()
{
  Program* p = parse_block();
  if (look_ahead() == EOS)
    return p;
  else
    throw Not_parsed("end of stream expected");
}

string Parser::parse_identifier()
{
  string s;
  while (isalnum(input[position]))
  {
    s.push_back(input[position]);
    position++;
  }
  return s;
}

Program* Parser::parse_block()
{
  Program* p = parse_instruction();
  
  char c = look_ahead();
  while(c != '}' && c != EOS)
  {
    Program* q = parse_instruction();
    p = new Composition(p, q);
    c = look_ahead();
  }

  return p;
}

Program* Parser::parse_instruction()
{
  char c = look_ahead();
  if (c == '{')
  {
    position++;
    Program* p = parse_block();
    if (look_ahead() == '}')
    {
      position++;
      return p;
    }
    else
      throw Not_parsed("'}' expected");
  }
  else if (isalpha(c))
  {
    string s = parse_identifier();
    
    if (s == "read")
      return parse_Read();
    else if (s == "write")
      return parse_Write();
    else if (s == "if")
      return parse_If();
    else if (s == "while")
      return parse_While();
    else if (s == "skip")
      return new Skip();
    else
      return parse_Assign(s);
  }
  else
    throw Not_parsed("identifier or a keyword expected");
}

Program* Parser::parse_Read()
{
  char c = look_ahead();
  if (isalpha(c))
  {
    string s = parse_identifier();
    return new Read(s);
  }
  else
    throw Not_parsed();
}

Program* Parser::parse_Write()
{
  char c = look_ahead();
  if (isalpha(c))
  {
    string s = parse_identifier();
    return new Write(s);
  }
  else
    throw Not_parsed("variable expected");
}

Program* Parser::parse_If()
{
    Expression* e = parse_sum();
    Program* p = parse_instruction();
    if (isalpha(look_ahead()))
    {
      if(parse_identifier() == "else")
      {
        Program* q = parse_instruction();
        return new If(e, p, q);
      }
      else
        throw Not_parsed("'else' expected");
    }
    else
      throw Not_parsed("'else' expected");
}

Program* Parser::parse_While()
{
    Expression* e = parse_sum();
    Program* p = parse_instruction();
    return new While(e, p);
}

Program* Parser::parse_Assign(string v)
{
  char c = look_ahead();
  if (c == '=')
  {
    position++;
    Expression* e = parse_sum();
    return new Assign(v, e);
  }
  else
    throw Not_parsed("'=' expected");
}

Parser::Parser(string input) : input(input), position(0)
{
  input.push_back(EOS);
}

void Parser::skip_whitespace()
{
  while (isspace(input[position]))
    position++;
}

char Parser::look_ahead()
{
  skip_whitespace();
  return input[position];
}

Expression* Parser::parse_sum()
{
  Expression* e = parse_mult();
  
  char c = look_ahead();
  while(c == '+' || c == '-')
  {
    position++;
    Expression* f = parse_mult();
    e = new Binary_operator(c, e, f);
    c = look_ahead();
  }

  return e;
}

Expression* Parser::parse_mult()
{
  Expression* e = parse_term();
  
  char c = look_ahead();
  while(c == '*' || c == '/' || c == '%')
  {
    position++;
    Expression* f = parse_term();
    e = new Binary_operator(c, e, f);
    c = look_ahead();
  }

  return e;
}

Expression* Parser::parse_term()
{
  char c = look_ahead();
  if (c == '(')
    return parse_paren();
  else if (isdigit(c))
    return parse_Constant();
  else if (isalpha(c))
    return parse_Variable();
  else
    throw Not_parsed("a term expected");
}

Expression* Parser::parse_Constant()
{
  int n = 0;
  while (isdigit(input[position]))
  {
    n *= 10;
    n += input[position] - '0';
    position++;
  }
  return new Constant(n);
}

Expression* Parser::parse_Variable()
{
  string s;
  while (isalnum(input[position]))
  {
    s.push_back(input[position]);
    position++;
  }
  return new Variable(s);
}

Expression* Parser::parse_paren()
{
  position++; // parse_term zapewnia, że wskaznik stoi na nawiasie otwierajacym '('
  Expression* e = parse_sum();
  if (look_ahead() == ')')
  {
    position++;
    return e;
  }
  else
    throw Not_parsed("')' expected");
}

// Main /////////////////////////////////////////////////////////////

int main()
{
  ifstream file;

  file.open("test.txt");
  if (!file)
  {
    cout << "ERROR: Unable to open file";
    return 1;
  }
  string s;
  getline(file, s, (char)EOF);
  file.close();

  try
  {
    Parser parser(s);
    Program* p = parser.parse_Program();
    Memory m;
    p -> eval(m);
    cout << endl;
  }
  catch (Not_parsed& exc)
  {
    cout << "ERROR: " << endl << exc.reason << endl;
  }

  return 0;
}

