summaryrefslogtreecommitdiff
path: root/Build/source/utils/asymptote/process.cc
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2009-05-16 00:19:13 +0000
committerKarl Berry <karl@freefriends.org>2009-05-16 00:19:13 +0000
commitbab45528d65eaafe68a705dbb2a57075c7b7cbd8 (patch)
tree10b4ae2b5195c8dede153ab89359ec00f55f325f /Build/source/utils/asymptote/process.cc
parent8643d90372e9c31e0f461c15c596b60a545bd7d3 (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.cc883
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);
+}
+