/***** * 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 "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 "process.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 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. 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("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("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("parseonly")) doParse(); else if (getSetting("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())); r->e.list(r); } } void itree::run(coenv &e, istack &s, transMode tm) { block *tree=getTree(); if (tree) { for(mem::list::iterator r=tree->stms.begin(); r != tree->stms.end(); ++r) { processData().fileName=(*r)->getPos().filename(); if(!em.errors() || getSetting("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("quiet")) { cout << "Welcome to " << PROGRAM << " 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("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 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.pl 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(!getSetting("xasy")) prompt=getSetting(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) { #ifdef __MSDOS__ const string EOT="\x04\r\n"; #else const string EOT="\x04\n"; #endif 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("multiline")) { block *code=parseExtendableLine(line); icode i(code); i.run(e,s,TRANS_INTERACTIVE); } else if(getSetting("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(eof&) { 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("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); }