0

I am currently working on adding embedded Python support (and yes, extending is not an option) to a large program as part of my summer internship. Ideally, I can keep the Python support within a single .DLL, which currently contains the program's in-house scripting language and is the easiest place to integrate said language and Python.

However, due to the program's API, I only have a single input function to use. This function's return value is a single line from the current input, which could be the console or a file. The input interface cannot (within the .DLL) be converted into a stream object, buffer, or FILE pointer.

My current test code (written outside of the program, using std::string, istream, and getline to ape the restriction) is

// start python
Py_Initialize();

try
{
   cout << "Python " << Py_GetVersion() << endl;
   string block;
   bool in_block = false;
   while ( !cin.eof() )
   {
      string str;
      cout << (in_block ? "... " : ">>> "); // prompt string
      getline(cin,str);

      if ( in_block ) // if in an indented block
      {
         if ( str.front() != ' ' && str.front() != '\t' ) // if ending the indented block
         {
            PyRun_SimpleString(block.c_str());  // run Python code contained in block string
            block.clear();                      // clear string for next block
            in_block = false;                   // no longer in block
         }
         else // a component of an indented block
         {
            block += (str+'\n'); // append input to block string
            continue;            // do not pass block exit code, do not collect two hundred dollars
         }
      }

      // either not in an indented block, or having just come out of one
      if ( str.back() == ':' ) // if colon, start new indented block
      {
         block = (str+'\n');
         in_block = true;
         continue;
      }
      else { PyRun_SimpleString(str.c_str()); } // otherwise, run block-free code
   }
}
catch ( error_already_set e ) { PyErr_Print(); }

// close python
Py_Finalize();

// done
return 0;

I have not encountered serious problems with this hack, but it strikes me as woefully inelegant. Can anyone think of a better way to do this?

I have already cleared the boost.python libraries with my boss, if that offers some useful trick that eluded me.

EDIT: I should probably mention the program itself, while not my meagre testbed, must run on MS Windows.

J.T. Davies
  • 401
  • 3
  • 9

1 Answers1

0

What you've written is going to look superficially similar to the stock interpreter, but it won't follow the same indent/dedent and continuation rules for any but the most trivial cases.

The easiest way to embed an interactive interpreter shell is to embed a bare interpreter that runs an interactive interpreter written in pure Python, usually via the code module.

For that to work, you will have to hook up your line-by-line reader to the embedded stdin file object, but it seems like for any realistic use you're going to want that anyway. (Otherwise, what's going to happen if, e.g., the user types input() at the shell?)

An alternative is to set up a pty and run the stock interactive interpreter against that (possibly as a child process), feeding the pty's input pipe from your line reader.

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • This initial code is not meant to cover every usage case, and is certainly not intended to be a full interpreter; I would need to add things like line continuation support for the full program, but I do not want to do that until I am sure the hack above is the only way. Running the stock interpreter against a pseudoterminal sounds like an excellent idea, though. – J.T. Davies May 28 '13 at 22:53
  • The fact that the actual program needs to run on Windows might also be a problem; not sure how that OS handles `pty`s. – J.T. Davies May 28 '13 at 23:00
  • @J.T.Davies: Windows doesn't have pty's. There are various different ways to fake it. When I had to do something similar years ago, I wrote almost completely separate code for Windows and everything else. IIRC, the Windows code, instead of faking out isatty, actually launched a real console app with a hidden console window as a child process and talked to the console window, or something horrible like that. But that may have only been necessary because I had to deal with Win9x, which hopefully isn't an issue nowadays… – abarnert May 29 '13 at 00:03
  • I had another thought: IDLE has its own robust, portable wrappers for running an interactive interpreter that thinks it owns a console, but is actually talking to the IDLE GUI. I don't know how easy they are to extract from the rest of IDLE, but it may be worth a look. (I believe IPython also has similar "remoting" support, but it's being phased out in favor of the new zeromq-based design that isn't finished yet.) – abarnert May 29 '13 at 00:17