// ISO C++ interpreter for ETA
// Original version by Eric Schmidt <eric41293@comcast.net>

// Copyright (c) 2004 Eric Schmidt

// Permission is granted to copy, modify, distribute, and sell copies of
// this software provided that this and the above notices are retained
// and that all modified versions that are distributed are clearly marked
// as being modified.

// NB: This program performs no error checking at run time. If you try to
// divide by 0 or anything else you're not allowed to do, the behavior is
// not defined.

#include <algorithm>
#include <cctype>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>

using std::cerr;
using std::cin;
using std::cout;
using std::div;
using std::exit;
using std::find;
using std::ifstream;
using std::ios;
using std::istream;
using std::ldiv_t;
using std::string;
using std::tolower;
using std::vector;

// Stubs which you should provide suitable definitions for if native charset
// is not compatible with ASCII.

inline signed char ascii_to_native(signed char c) { return c; }
inline signed char native_to_ascii(signed char c) { return c; }

struct Program {
  typedef string::size_type size_type;   // Size type for program

  string code;               // Instructions
  vector<size_type> addrs;   // Addresses of beginnings of lines
  vector<long> stack;        // The program stack
                             // We can't use a std::stack for this because we
                             // wouldn't be able to implement Halibut  
  Program() : addrs(1, 0) {} // First line always starts at 0
};

void parse(istream&, Program&);
void execute(Program&);
int letter_to_digit(char);

int main(int argc, char *argv[]) {
  Program prog;                          // The compiled code 

  // Read arguments
  if (argc < 2) parse(cin, prog);
  else
    while (*++argv) {
      ifstream f(*argv, ios::in);
      if (!f) {
        cerr << "eta: Unable to open " << *argv << '\n';
        exit(EXIT_FAILURE);
      }
      parse(f, prog);
    }

  execute(prog);
}

// Read program into program from stream.
void parse(istream& in, Program& out) {
  const string sigchars = "etaoinsh\n";  // The significant characters
  char c;

  Program::size_type charnum = out.code.size();

  while ((c = in.get()) != EOF) {
    int cl = tolower(c);
    if (find(sigchars.begin(), sigchars.end(), cl) != sigchars.end()) { 
      out.code.push_back(cl);
      ++charnum;
      if (cl == '\n') out.addrs.push_back(charnum);
    }
  }
}

// Execute the program

void execute(Program& prog) {
  long line = 1;                 // Current line number  

  for (string::iterator PC = prog.code.begin(); PC != prog.code.end();) {
    switch (*PC++) {

      case '\n': ++line; break;

      // devidE
      case 'e': {
        long divisor  = prog.stack.back(); prog.stack.pop_back();
        long dividend = prog.stack.back(); prog.stack.pop_back();
        ldiv_t result = div(dividend, divisor);
        prog.stack.push_back(result.quot);
        prog.stack.push_back(result.rem);
        break;
      }

      // Transfer
      case 't': {
        long addr = prog.stack.back(); prog.stack.pop_back();
        long cond = prog.stack.back(); prog.stack.pop_back();
        if (cond) {
          if (addr == 0) return;          // 0 means exit program
          PC = prog.code.begin() + prog.addrs[addr - 1];
        }
        line = addr;
        break;
      }

      // Address
      case 'a': {
        prog.stack.push_back(line + 1);
        break;
      }

      // Output
      case 'o': {
        // Intentional downcast
        signed char outchar = prog.stack.back(); prog.stack.pop_back();
        cout << ascii_to_native(outchar);
        break;
      }

      // Input
      case 'i': {
        signed char inchar;
        inchar = cin.get();
        if (cin.eof()) inchar = -1;    // EOF means push -1
          else inchar = native_to_ascii(inchar);
        prog.stack.push_back(inchar);
        break;
      }

      // Number
      case 'n': {
        // Why parse the number now, rather than when compiling?
        // Well, if a number sequence crosses a newline, then it is possible
        // for some of it to be also used as instructions if you transfer to
        // that line. So we have to interpret on a case-by-case basis.

        long number = 0;
        char letter;

        while ((letter = *PC++) != 'e') {
          if (letter == '\n')
            ++line;
          else {
            number *= 7;
            number += letter_to_digit(letter);
          }
        }
        prog.stack.push_back(number);
        break;
      }

      // Subtract
      case 's': {
        long op2 = prog.stack.back(); prog.stack.pop_back();
        long op1 = prog.stack.back(); prog.stack.pop_back();
        prog.stack.push_back(op1 - op2);
        break;
      }

      // Halibut
      case 'h': {
        long op = prog.stack.back(); prog.stack.pop_back();

        if (op > 0) {
          // roll if positive
          long val =     *(prog.stack.end() - op - 1);
          prog.stack.erase(prog.stack.end() - op - 1);
          prog.stack.push_back(val);
        } else {
          // duplicate if negative or 0
          prog.stack.push_back(*(prog.stack.end() + op - 1));
        }
      break;
      }
    }
  }
}

int letter_to_digit(char c) {
  switch (c) {
    case 'h': return 0;
    case 't': return 1;
    case 'a': return 2;
    case 'o': return 3;
    case 'i': return 4;
    case 'n': return 5;
    case 's': return 6;
  }
  abort();
  return 0;
}

