/*****
* process.cc
* Andy Hammerlindl 2006/08/19
*
* Handles processing blocks of code (including files, strings, and the
* interactive prompt, for listing and parse-only modes as well as actually
* running it.
*****/

#include "types.h"
#include "errormsg.h"
#include "genv.h"
#include "coenv.h"
#include "stm.h"
#include "settings.h"
#include "vm.h"
#include "program.h"
#include "interact.h"
#include "envcompleter.h"

#include "fileio.h"

#include "stack.h"
#include "runtime.h"
#include "texfile.h"

#include "asyprocess.h"

namespace camp {
pen& defaultpen() {
 return processData().defaultpen;
}
}

unsigned int count=0;

namespace run {
void cleanup();
void exitFunction(vm::stack *Stack);
void updateFunction(vm::stack *Stack);
void purge(Int divisor=0);
}

namespace vm {
bool indebugger;
}

using namespace settings;

using absyntax::file;
using trans::genv;
using trans::coenv;
using trans::env;
using trans::coder;
using types::record;

using interact::interactive;
using interact::uptodate;

using absyntax::runnable;
using absyntax::block;

mem::stack<processDataStruct *> processDataStack;

// Exception-safe way to push and pop the process data.
class withProcessData {
 // Do not let this object be dynamically allocated.
 void *operator new(size_t);

 processDataStruct *pd_ptr;
public:

 withProcessData(processDataStruct *pd_ptr) : pd_ptr(pd_ptr)
 {
   processDataStack.push(pd_ptr);
 }

 ~withProcessData()
 {
   assert(processDataStack.top() == pd_ptr);
   processDataStack.pop();
 }
};

processDataStruct &processData() {
 assert(!processDataStack.empty());
 return *processDataStack.top();
}

// A process environment.  Basically this just serves as short-hand to start a
// new global environment (genv) and new process data at the same time.  When
// it goes out of scope, the process data is popped off the stack.  This also
// ensures that the data is popped even if an exception is thrown.
class penv {
 genv *_ge;
 processDataStruct _pd;

 // Do not let this object be dynamically allocated.
 void *operator new(size_t);

public:
 penv() : _ge(0), _pd() {
   // Push the processData first, as it needs to be on the stack before genv
   // is initialized.
   _pd.topPos = nullPos;
   processDataStack.push(&_pd);
   _ge = new genv;
 }

 virtual ~penv() {
   processDataStack.pop();
   delete _ge;
 }

 genv &ge() { return *_ge; }

 processDataStruct *pd() { return &_pd; }
};


void init(bool resetpath=true)
{
 vm::indebugger=false;
 uptodate=false;
 if(resetpath)
   setPath("");  /* On second and subsequent calls, sets the path
                    to what it was when the program started. */
}

// This helper class does nothing but call the interactiveTrans method of the
// base object in place of trans, so that the runnable can exhibit special
// behaviour when run at the interactive prompt.
class interactiveRunnable : public runnable {
 runnable *base;

public:
 interactiveRunnable(runnable *base)
   : runnable(base->getPos()), base(base) {}

 void prettyprint(ostream &out, Int indent) {
   absyntax::prettyname(out, "interactiveRunnable", indent, base->getPos());
   base->prettyprint(out, indent+1);
 }

 void trans(coenv &e) {
   base->interactiveTrans(e);
 }

 void transAsField(coenv &e, types::record *r) {
   // There is no interactiveTransAsField, as fields aren't declared at the top
   // level of the interactive prompt.
   base->transAsField(e, r);
 }
};




// How to run a runnable in runnable-at-a-time mode.
bool runRunnable(runnable *r, coenv &e, istack &s, transMode tm=TRANS_NORMAL) {
 e.e.beginScope();

 lambda *codelet= tm==TRANS_INTERACTIVE ?
   interactiveRunnable(r).transAsCodelet(e) :
   r->transAsCodelet(e);
 em.sync();
 if(!em.errors()) {
   if(getSetting<bool>("translate")) print(cout,codelet->code);
   s.run(codelet);

   // Commits the changes made to the environment.
   e.e.collapseScope();
 } else {
   e.e.endScope(); // Remove any changes to the environment.

   // Should an interactive error hurt the status?
   em.statusError();

   return false;
 }
 return true;
}

void runAutoplain(coenv &e, istack &s) {
 absyntax::runnable *r=absyntax::autoplainRunnable();
 runRunnable(r,e,s);
}

// icore


// preRun and postRun are the optional activities that take place before and
// after running the code specified.  They can be overridden by a derived
// class that wishes different behaviour.
void icore::preRun(coenv &e, istack &s) {
 if(getSetting<bool>("autoplain"))
   runAutoplain(e,s);
}

void icore::postRun(coenv &, istack &s) {
 run::exitFunction(&s);
}

void icore::doRun(bool purge, transMode tm) {
 em.sync();
 if(em.errors())
   return;

 try {
   if(purge) run::purge();

   penv pe;
   env base_env(pe.ge());
   coder base_coder(nullPos, "icore::doRun");
   coenv e(base_coder,base_env);

   vm::interactiveStack s;
   s.setInitMap(pe.ge().getInitMap());
   s.setEnvironment(&e);

   preRun(e,s);

   if(purge) run::purge();

   // Now that everything is set up, run the core.
   run(e,s,tm);

   postRun(e,s);

 } catch(std::bad_alloc&) {
   outOfMemory();
 } catch(quit const&) {
   // Exception to quit running the current code. Nothing more to do.
 } catch(handled_error const&) {
   em.statusError();
 }

 run::cleanup();

 em.clear();
}


void icore::process(bool purge) {
 if (!interactive && getSetting<bool>("parseonly"))
   doParse();
 else if (getSetting<bool>("listvariables"))
   doList();
 else
   doRun(purge);
}

itree::itree(string name) : name(name), cachedTree(0) { }


block *itree::getTree() {
 if (cachedTree==0) {
   try {
     cachedTree=buildTree();
   } catch(handled_error const&) {
     em.statusError();
     return 0;
   }
 }
 return cachedTree;
}

string itree::getName() {
 return name;
}

void itree::doParse() {
 block *tree=getTree();
 em.sync();
 if(tree && !em.errors())
   tree->prettyprint(cout, 0);
}

void itree::doList() {
 block *tree=getTree();
 if (tree) {
   penv pe;
   record *r=tree->transAsFile(pe.ge(), symbol::trans(getName()));
   if(r)
     r->e.list(r);
 }
}

void itree::run(coenv &e, istack &s, transMode tm) {
 block *tree=getTree();
 if (tree) {
   for(mem::list<runnable *>::iterator r=tree->stms.begin();
       r != tree->stms.end(); ++r) {
     processData().fileName=(*r)->getPos().filename();
     if(!em.errors() || debug)
       runRunnable(*r,e,s,tm);
   }
 }
}

void itree::doExec(transMode tm) {
 // Don't prepare an environment to run the code if there isn't any code.
 if (getTree())
   icore::doRun(false,tm);
}

void printGreeting(bool interactive) {
 if(!getSetting<bool>("quiet")) {
   cout << "Welcome to " << PACKAGE_NAME << " version " << REVISION;
   if(interactive)
     cout << " (to view the manual, type help)";
   cout << endl;
 }
}

ifile::ifile(const string& filename)
 : itree(filename),
   filename(filename),
   outname(stripDir(stripExt(string(filename == "-" ? settings::outname() : filename), suffix))) {}

block *ifile::buildTree() {
 return !filename.empty() ? parser::parseFile(filename,"Loading") : 0;
}

void ifile::preRun(coenv& e, istack& s) {
 outname_save=getSetting<string>("outname");
 if(stripDir(outname_save).empty())
   Setting("outname")=outname_save+outname;

 itree::preRun(e, s);
}

void ifile::postRun(coenv &e, istack& s) {
 itree::postRun(e, s);

 Setting("outname")=outname_save;
}

void ifile::process(bool purge) {
 if(verbose > 1) printGreeting(false);
 try {
   init();
 } catch(handled_error const&) {
 }

 if (verbose >= 1)
   cout << "Processing " << outname << endl;

 try {
   icore::process(purge);
 }
 catch(handled_error const&) {
   em.statusError();
 }
}

// Add a semi-colon terminator, if one is not there.
string terminateLine(const string line) {
 return (!line.empty() && *(line.rbegin())!=';') ? (line+";") : line;
}

// cleanLine changes a C++ style comment (//) into a C-style comment (/* */) so
// that comments don't absorb subsequent lines of code when multiline input is
// collapsed to a single line in the history.
//
// ex.  if (x==1) // test x
//        x=2;
// becomes
//      if (x==1) /* test x */ x=2    (all on one line)
//
// cleanLine is a mess because we have to check that the // is not in a string
// or c-style comment, which entails re-inventing much of the lexer.  The
// routine handles most cases, but multiline strings lose their newlines when
// recorded in the history.
typedef string::size_type size_type;
const size_type npos=string::npos;

inline size_type min(size_type a, size_type b) {
 return a < b ? a : b;
}

// If start is an offset somewhere within a string, this returns the first
// offset outside of the string. sym is the character used to start the string,
// ' or ".
size_type endOfString(const char sym, const string line, size_type start) {
 size_type endString=line.find(sym, start);
 if (endString == npos)
   return npos;

 size_type escapeInString=min(line.find(string("\\")+sym, start),
                              line.find("\\\\", start));
 if (endString < escapeInString)
   return endString+1;
 else
   return endOfString(sym, line, escapeInString+2);
}

// If start is an offset somewhere within a C-style comment, this returns the
// first offset outside of the comment.
size_type endOfComment(const string line, size_type start) {
 size_type endComment=line.find("*/", start);
 if (endComment == npos)
   return npos;
 else
   return endComment+2;
}

// Find the start of a string literal in the line.
size_type stringPos(const string line, size_type start) {
 if (start == npos)
   return npos;

 size_type pos=line.find_first_of("\'\"", start);
 if (pos == npos)
   return npos;

 // Skip over comments /*  */ and ignore anything after //
 size_type startComment=line.find("/*", start);
 size_type startLineComment=line.find("//", start);

 if (min(startComment,startLineComment) < pos)
   return stringPos(line,
                    startComment < startLineComment ?
                    endOfComment(line, startComment+2) :
                    npos);
 else
   // Nothing to skip over - the symbol actually starts a string.
   return pos;
}

// A multiline string should retain its newlines when collapsed to a single
// line.  This converts
//   'hello
//   there'
// to
//   'hello\nthere'
// and
//   "hello
//   there"
// to
//   "hello" '\n' "there"
// If the line doesn't end mid-string, this adds a space to the end to preserve
// whitespace, since it is the last function to touch the line.
string endString(const string line, size_type start) {
 assert(start!=npos);

 size_type pos=stringPos(line, start);

 if (pos==npos)
   // String ends in normal code.
   return line+" ";
 else {
   char sym=line[pos];
   size_type eos=endOfString(sym, line, pos+1);
   if (eos==npos) {
     // Line ends while in a string, attach a newline symbol.
     switch (line[pos]) {
       case '\'':
         return line+"\\n";
       case '\"':
         return line+"\" \'\\n\' \"";
       default:
         assert(False);
         return line;
     }
   }
   else {
     return endString(line, eos+1);
   }
 }
}

// Find the first // that isn't in a C-style comment or a string.
size_type slashPos(const string line, size_type start) {
 if (start == npos)
   return npos;

 size_type pos=line.find("//", start);
 if (pos == npos)
   return npos;

 // Skip over comments /*  */ and strings both "   " and '   '
 size_type startComment=line.find("/*", start);
 size_type startString=line.find_first_of("\'\"", start);

 if (min(startComment,startString) < pos)
   return slashPos(line,
                   startComment < startString ?
                   endOfComment(line, startComment+2) :
                   endOfString(line[startString], line, startString+1));
 else
   // Nothing to skip over - the // actually starts a comment.
   return pos;
}

string endCommentOrString(const string line) {
 size_type pos=slashPos(line, 0);
 if (pos == npos)
   return endString(line, 0);
 else {
   string sub=line;

   // Replace the first // by /*
   sub[pos+1]='*';

   // Replace any */ in the comment by *!
   while ((pos = line.find("*/", pos+2)) != npos)
     sub[pos+1]='!';

   // Tack on a */ at the end.
   sub.append(" */ ");
   return sub;
 }
}

bool isSlashed(const string line) {
 // NOTE: This doesn't fully handle escaped slashed in a string literal.
 unsigned n=line.size();
 return n > 0 ? line[line.size()-1] == '\\' : false;
}
string deslash(const string line) {
 return isSlashed(line) ? line.substr(0,line.size()-1) : line;
}

// This transforms a line in to the history, so that when more code is added
// after it, the code behaves the same as if there was a newline between the
// two lines.  This involves changing // style comments to /* */ style comments,
// and adding explicit newlines to multiline strings.
string cleanLine(const string line) {
 // First remove a trailing slash, if there is one.
 return endCommentOrString(deslash(line));
}

class iprompt : public icore {
 // Flag that is set to false to signal the prompt to exit.
 bool running;

 // Flag that is set to restart the main loop once it has exited.
 bool restart;

 // Code ran at start-up.
 string startline;

 void postRun(coenv &, istack &) {
 }

 // Commands are chopped into the starting word and the rest of the line.
 struct commandLine {
   string line;
   string word;
   string rest;

   commandLine(string line) : line(line) {
     string::iterator c=line.begin();

     // Skip leading whitespace
     while (c != line.end() && isspace(*c))
       ++c;

     // Only handle identifiers starting with a letter.
     if (c != line.end() && isalpha(*c)) {
       // Store the command name.
       while (c != line.end() && (isalnum(*c) || *c=='_')) {
         word.push_back(*c);
         ++c;
       }
     }

     // Copy the rest to rest.
     while (c != line.end()) {
       rest.push_back(*c);
       ++c;
     }

#if 0
     cerr << "line: " << line << endl;
     cerr << "word: " << word << endl;
     cerr << "rest: " << rest << endl;
     cerr << "simple: " << simple() << endl;
#endif
   }

   // Simple commands have at most spaces or semicolons after the command word.
   bool simple() {
     for (string::iterator c=rest.begin(); c != rest.end(); ++c)
       if (!isspace(*c) && *c != ';')
         return false;
     return true;
   }
 };


 // The interactive prompt has special functions that cannot be implemented as
 // normal functions.  These special funtions take a commandLine as an argument
 // and return true if they can handle the command.  If false is returned, the
 // line is treated as a normal line of code.
 // commands is a map of command names to methods which implement the commands.
 typedef bool (iprompt::*command)(coenv &, istack &, commandLine);
 typedef mem::map<const string, command> commandMap;
 commandMap commands;

 bool exit(coenv &, istack &, commandLine cl) {
   if (cl.simple()) {
     // Don't store exit commands in the history file.
     interact::deleteLastLine();
     running=false;
     return true;
   }
   else return false;
 }

 bool q(coenv &e, istack &s, commandLine cl) {
   if(e.e.ve.getType(symbol::trans("q"))) return false;
   return exit(e,s,cl);
 }

 bool reset(coenv &, istack &, commandLine cl) {
   if (cl.simple()) {
     running=false;
     restart=true;
     startline="";
     run::purge();
     return true;
   }
   else return false;
 }

 bool help(coenv &, istack &, commandLine cl) {
   if (cl.simple()) {
     popupHelp();
     return true;
   }
   else return false;
 }

 bool erase(coenv &e, istack &s, commandLine cl) {
   if (cl.simple()) {
     runLine(e,s,"erase();");
     return true;
   }
   else return false;
 }

 bool input(coenv &, istack &, commandLine cl) {
   running=false;
   restart=true;
   startline="include "+cl.rest;
   return true;
 }

 void initCommands() {
#define ADDCOMMAND(name, func)                  \
   commands[#name]=&iprompt::func

   // keywords.py looks for ADDCOMMAND to identify special commands in the
   // auto-completion.
   ADDCOMMAND(quit,exit);
   ADDCOMMAND(q,q);
   ADDCOMMAND(exit,exit);
   ADDCOMMAND(reset,reset);
   ADDCOMMAND(erase,erase);
   ADDCOMMAND(help,help);
   ADDCOMMAND(input,input);

#undef ADDCOMMAND
 }

 bool handleCommand(coenv &e, istack &s, string line) {
   commandLine cl(line);
   if (cl.word != "") {
     commandMap::iterator p=commands.find(cl.word);
     if (p != commands.end()) {
       // Execute the command.
       command &com=p->second;
       return (this->*com)(e,s,cl);
     }
     else
       return false;
   }
   else
     return false;
 }

 void addToHistory(string line) {
   interact::addToHistory(line);
 }

 void addToLastLine(string line) {
   // Here we clean a line at the last possible point, when we know that more
   // code is going to be appended to it.
   string last=interact::getLastHistoryLine();
   interact::setLastHistoryLine(cleanLine(last)+line);
 }

 void terminateLastHistoryLine() {
   string last=interact::getLastHistoryLine();
   interact::setLastHistoryLine(terminateLine(last));
 }

 // Get input from the interactive prompt.  Such input may be over several
 // user-typed lines if he/she ends a line a with backslash to continue input
 // on the next line.  If continuation is true, the input started on a previous
 // line and is being continued (either because of a backslash or the parser
 // detecting it in multiline mode).
 string getline(bool continuation) {
   string prompt;
   if(!settings::xasy)
     prompt=getSetting<string>(continuation ? "prompt2" : "prompt");
   string line=interact::simpleline(prompt);

   if (continuation)
     addToLastLine(line);
   else
     addToHistory(line);

   // If the line ends in a slash, get more input.
   return isSlashed(line) ? line+"\n"+getline(true) :
     line;
 }

 // Continue taking input for a line until it properly parses, or a syntax
 // error occurs.  Returns the parsed code on success, and throws a
 // handled_error exception on failure.
 block *parseExtendableLine(string line) {
   block *code=parser::parseString(line, "-", true);
   if (code) {
     return code;
   } else {
     string nextline=getline(true);
     return parseExtendableLine(line+"\n"+nextline);
   }
 }

 // Continue taking input until a termination command is received from xasy.
 block *parseXasyLine(string line) {
   const string EOT="\x04\n";
   string s;
   while((s=getline(true)) != EOT)
     line += s;
   return parser::parseString(line, "-", true);
 }

 void runLine(coenv &e, istack &s, string line) {
   try {
     if(getSetting<bool>("multiline")) {
       block *code=parseExtendableLine(line);

       icode i(code);
       i.run(e,s,TRANS_INTERACTIVE);
     } else if(settings::xasy) {
       block *code=parseXasyLine(line);

       icode i(code);
       i.run(e,s,TRANS_INTERACTIVE);
     } else {
       // Add a semi-colon to the end of the line if one is not there.  Do this
       // to the history as well, so the transcript can be run as regular asy
       // code.  This also makes the history work correctly if the multiline
       // setting is changed within an interactive session.
       // It is added to the history at the last possible moment to avoid
       // tampering with other features, such as using a slash to extend a
       // line.
       terminateLastHistoryLine();
       istring i(terminateLine(line), "-");
       i.run(e,s,TRANS_INTERACTIVE);
     }

     run::updateFunction(&s);
     uptodate=false;

   } catch(handled_error const&) {
     vm::indebugger=false;
   } catch(interrupted&) {
     // Turn off the interrupted flag.
     em.Interrupt(false);
     uptodate=true;
     cout << endl;
   } catch(quit&) {
   }

   // Ignore errors from this line when trying to run subsequent lines.
   em.clear();
 }

 void runStartCode(coenv &e, istack &s) {
   if (!startline.empty())
     runLine(e, s, startline);
 }

public:
 iprompt() : running(false), restart(false), startline("") {
   initCommands();
 }

 void doParse() {}

 void doList() {}

 void run(coenv &e, istack &s, transMode=TRANS_NORMAL) {
   running=true;
   interact::setCompleter(new trans::envCompleter(e.e));

   runStartCode(e, s);

   while (running) {
     // Read a line from the prompt.
     string line=getline(false);
     // Check if it is a special command.
     if (handleCommand(e,s,line))
       continue;
     else
       runLine(e, s, line);
   }
 }

 void process(bool purge=false) {
   printGreeting(true);
   interact::init_interactive();
   try {
     setPath("",true);
   } catch(handled_error const&) {
   }

   do {
     try {
       init(false);
       restart=false;
       icore::process();
     } catch(interrupted&) {
       em.Interrupt(false);
       restart=true;
     } catch(EofException&) {
       restart=false;
     }
   } while(restart);

   interact::cleanup_interactive();
 }
};

void processCode(absyntax::block *code) {
 icode(code).process();
}
void processFile(const string& filename, bool purge) {
 ifile(filename).process(purge);
}
void processPrompt() {
 iprompt().process();
}

void runCode(absyntax::block *code) {
 icode(code).doExec();
}
void runString(const string& s, bool interactiveWrite) {
 istring(s).doExec(interactiveWrite ? TRANS_INTERACTIVE : TRANS_NORMAL);
}
void runFile(const string& filename) {
 ifile(filename).doExec();
}
void runPrompt() {
 iprompt().doRun();
}

void runCodeEmbedded(absyntax::block *code, trans::coenv &e, istack &s) {
 icode(code).run(e,s);
}
void runStringEmbedded(const string& str, trans::coenv &e, istack &s) {
 istring(str).run(e,s);
}
void runPromptEmbedded(trans::coenv &e, istack &s) {
 iprompt().run(e,s);
}

void doUnrestrictedList() {
 penv pe;
 env base_env(pe.ge());
 coder base_coder(nullPos, "doUnrestictedList");
 coenv e(base_coder,base_env);

 if (getSetting<bool>("autoplain"))
   absyntax::autoplainRunnable()->trans(e);

 e.e.list(0);
}

// Environment class used by external programs linking to the shared library.
class fullenv : public gc {
 penv pe;
 env base_env;
 coder base_coder;
 coenv e;

 vm::interactiveStack s;

public:
 fullenv() :
   pe(), base_env(pe.ge()), base_coder(nullPos, "fullenv"),
   e(base_coder, base_env), s()
 {
   s.setInitMap(pe.ge().getInitMap());
   s.setEnvironment(&e);

   // TODO: Add way to not run autoplain.
   runAutoplain(e, s);
 }

 coenv &getCoenv()
 {
   return e;
 }

 void runRunnable(runnable *r)
 {
   assert(!em.errors()); // TODO: Decide how to handle prior errors.

   try {
     { withProcessData token(pe.pd());
       ::runRunnable(r, e, s, TRANS_INTERACTIVE);
     }
   } catch(std::bad_alloc&) {
     // TODO: give calling application useful message.
     cerr << "out of memory" << endl;
   } catch (quit const&) {
     // I'm not sure whether this counts as successfully running the code or
     // not.
     cerr << "quit exception" << endl;
   } catch (handled_error const&) {
     // Normally, this is the result of an error that changes the return code
     // of the free-standing asymptote program.
     // An error should probably be reported to the application calling the
     // asymptote library.
     cerr << "handled error" << endl;
   }

   em.clear();
 }
};

fullenv &getFullEnv()
{
 static fullenv fe;
 return fe;
}

coenv &coenvInOngoingProcess()
{
 return getFullEnv().getCoenv();
}

void runInOngoingProcess(runnable *r)
{
 getFullEnv().runRunnable(r);
}