diff options
author | Karl Berry <karl@freefriends.org> | 2009-05-16 00:19:13 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2009-05-16 00:19:13 +0000 |
commit | bab45528d65eaafe68a705dbb2a57075c7b7cbd8 (patch) | |
tree | 10b4ae2b5195c8dede153ab89359ec00f55f325f /Build/source/utils/asymptote/process.cc | |
parent | 8643d90372e9c31e0f461c15c596b60a545bd7d3 (diff) |
asymptote 1.72 sources (not integrated into build yet)
git-svn-id: svn://tug.org/texlive/trunk@13110 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Build/source/utils/asymptote/process.cc')
-rw-r--r-- | Build/source/utils/asymptote/process.cc | 883 |
1 files changed, 883 insertions, 0 deletions
diff --git a/Build/source/utils/asymptote/process.cc b/Build/source/utils/asymptote/process.cc new file mode 100644 index 00000000000..59fb8c358fa --- /dev/null +++ b/Build/source/utils/asymptote/process.cc @@ -0,0 +1,883 @@ +/***** + * 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 "parser.h" +#include "fileio.h" + +#include "stack.h" +#include "runtime.h" +#include "texfile.h" + +#include "process.h" + +namespace camp { +pen& defaultpen() { + return processData().defaultpen; +} +} + +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; + +processDataStruct &processData() { + assert(!processDataStack.empty()); + return *processDataStack.top(); +} + +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->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); + } +}; + +enum transMode { + TRANS_INTERACTIVE, + TRANS_NORMAL +}; + + +// 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); +} + +// Abstract base class for the core object being run in line-at-a-time mode, it +// may be a block of code, file, or interactive prompt. +struct icore { + virtual ~icore() {} + + virtual void doParse() = 0; + virtual void doList() = 0; + +public: + + // preRun and postRun are the optional activities that take place before and + // after running the code specified. They can be overridden by derived class + // that wish different behaviour. + virtual void preRun(coenv &e, istack &s) { + if(getSetting<bool>("autoplain")) + runAutoplain(e,s); + } + + virtual void run(coenv &e, istack &s, transMode tm=TRANS_NORMAL) = 0; + + virtual void postRun(coenv &, istack &s) { + run::exitFunction(&s); + } + + virtual void doRun(bool purge=false, transMode tm=TRANS_NORMAL) { + em.sync(); + if(em.errors()) + return; + + try { + if(purge) run::purge(); + + genv ge; + env base_env(ge); + coder base_coder; + coenv e(base_coder,base_env); + + vm::interactiveStack s; + s.setInitMap(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) { + } catch(handled_error) { + em.statusError(); + } + + run::cleanup(); + + em.clear(); + } + + virtual void process(bool purge=false) { + if (!interactive && getSetting<bool>("parseonly")) + doParse(); + else { + // This is not done in preRun as it is not an optional step. + processDataStruct data; + processDataStack.push(&data); + + if (getSetting<bool>("listvariables")) + doList(); + else + doRun(purge); + + processDataStack.pop(); + } + } +}; + +// Abstract base class for one-time processing of an abstract syntax tree. +class itree : public icore { + string name; + + block *cachedTree; +public: + itree(string name="<unnamed>") + : name(name), cachedTree(0) {} + + // Build the tree, possibly throwing a handled_error if it cannot be built. + virtual block *buildTree() = 0; + + virtual block *getTree() { + if (cachedTree==0) { + try { + cachedTree=buildTree(); + } catch(handled_error) { + em.statusError(); + return 0; + } + } + return cachedTree; + } + + virtual string getName() { + return name; + } + + void doParse() { + block *tree=getTree(); + em.sync(); + if(tree && !em.errors()) + tree->prettyprint(cout, 0); + } + + void doList() { + block *tree=getTree(); + if (tree) { + genv ge; + record *r=tree->transAsFile(ge, symbol::trans(getName())); + r->e.list(r); + } + } + + void run(coenv &e, istack &s, transMode tm=TRANS_NORMAL) { + block *tree=getTree(); + if (tree) { + for(mem::list<runnable *>::iterator r=tree->stms.begin(); + r != tree->stms.end(); ++r) + if(!em.errors() || getSetting<bool>("debug")) + runRunnable(*r,e,s,tm); + } + } + + void doRun(transMode tm=TRANS_NORMAL) { + // Don't prepare an environment to run the code if there isn't any code. + if (getTree()) { + // This is not done in preRun as it is not an optional step. + processDataStruct data; + processDataStack.push(&data); + + icore::doRun(false,tm); + + processDataStack.pop(); + } + } +}; + +class icode : public itree { + block *tree; + +public: + icode(block *tree, string name="<unnamed>") + : itree(name), tree(tree) {} + + block *buildTree() { + return tree; + } +}; + +class istring : public itree { + string str; + +public: + istring(const string& str, string name="<eval>") + : itree(name), str(str) {} + + block *buildTree() { + return parser::parseString(str, getName()); + } +}; + +class ifile : public itree { + string filename; + string outname; + string outname_save; + +public: + ifile(const string& filename) + : itree(filename), + filename(filename), + outname((string) (filename == "-" ? settings::outname() : + stripDir(stripExt(string(filename), suffix)))) {} + + block *buildTree() { + return !filename.empty() ? parser::parseFile(filename,"Loading") : 0; + } + + void 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 postRun(coenv &e, istack& s) { + itree::postRun(e, s); + + Setting("outname")=outname_save; + } + + void process(bool purge=false) { + try { + init(); + } catch(handled_error) { + } + + if (verbose >= 1) + cout << "Processing " << outname << endl; + + try { + icore::process(purge); + } + catch(handled_error) { + em.statusError(); + } + } +}; + +void printGreeting() { + if(!getSetting<bool>("quiet")) + cout << "Welcome to " << PROGRAM << " version " << VERSION << SVN_REVISION + << " (to view the manual, type help)" << endl; +} + + +// 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. + return line[line.size()-1]=='\\'; +} +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; + + // Used for handling quit abbreviation (q). + protoenv *einteractive; + + 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)(commandLine); + typedef mem::map<CONST string, command> commandMap; + commandMap commands; + + bool exit(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(commandLine cl) { + if(einteractive->ve.getType(symbol::trans("q"))) return false; + return exit(cl); + } + + bool reset(commandLine cl) { + if (cl.simple()) { + running=false; + restart=true; + startline=""; + + run::purge(); + + return true; + } + else return false; + } + + bool help(commandLine cl) { + if (cl.simple()) { + popupHelp(); + return true; + } + else return false; + } + + + bool input(commandLine cl) { + string prefix="include "; + string line=prefix+cl.rest; + running=false; + restart=true; + startline=line; + 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(help,help); + ADDCOMMAND(input,input); + +#undef ADDCOMMAND + } + + bool handleCommand(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)(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=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); + } + } + + 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 { + // 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) { + 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)); + einteractive=&(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(line)) + continue; + else + runLine(e, s, line); + } + } + + void process() { + printGreeting(); + interact::init_interactive(); + try { + setPath("",true); + } catch(handled_error) { + } + + 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).doRun(); +} +void runString(const string& s, bool interactiveWrite) { + istring(s).doRun(interactiveWrite ? TRANS_INTERACTIVE : TRANS_NORMAL); +} +void runFile(const string& filename) { + ifile(filename).doRun(); +} +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() { + processDataStruct data; + processDataStack.push(&data); + + genv ge; + env base_env(ge); + coder base_coder; + coenv e(base_coder,base_env); + + if (getSetting<bool>("autoplain")) + absyntax::autoplainRunnable()->trans(e); + + processDataStack.pop(); + e.e.list(0); +} + |