/*****
* 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.
*****/
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);
// 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;
}
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;
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();
// 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);
}
// 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;
// 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;
}
// 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;
// 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;
}
// 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;
}
// 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);
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);
}
// 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);
}
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);
}
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);
}
}
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;
// 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;
}