Skip to content

Commit

Permalink
Add output buffers with realtime printing to frontend
Browse files Browse the repository at this point in the history
This allows to remove the do_print_output function, and stderr and stdout
are shown automatically

Close: #24
  • Loading branch information
rapgenic committed Feb 6, 2021
1 parent 36184c5 commit bf8d13e
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 66 deletions.
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ configure_file(
set(XEUS_OCTAVE_SRC
src/xoctave_interpreter.cpp
src/xoctave_interpreter.hpp
src/input.cpp
src/input.hpp
src/io.cpp
src/io.hpp
src/toolkits/plotly.cpp
src/toolkits/plotly.hpp
src/toolkits/notebook.cpp
Expand Down
56 changes: 42 additions & 14 deletions src/input.cpp → src/io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* along with xeus-octave. If not, see <http://www.gnu.org/licenses/>.
*/

#include "input.hpp"
#include "io.hpp"

#include <iostream>

Expand Down Expand Up @@ -55,31 +55,59 @@ void _reset_command_editor();

} // namespace

void input::set_command_editor(octave::command_editor& n) {
void input::override(input& n) {
_set_command_editor(n);
}
void input::reset_command_editor() {

void input::restore() {
_reset_command_editor();
}

input::input(std::function<std::string(const std::string&)> callback) : m_callback(std::move(callback)) {}

std::string input::do_readline(const std::string& prompt, bool&) {
auto& interpreter = dynamic_cast<xoctave_interpreter&>(xeus::get_interpreter());
return m_callback(prompt);
}

if (interpreter.m_allow_stdin) {
// Print any output if needed
interpreter.do_print_output();
void output::override(std::ostream& stream, output& buf) {
// Backup previous buffer
buf.p_oldbuf = stream.rdbuf();

// Perform the read request
auto input = xeus::blocking_input_request(prompt, false);
stream.rdbuf(&buf);
}

// Print newline
std::cout << std::endl;
interpreter.do_print_output();
void output::restore(std::ostream& stream, output& buf) {
stream.rdbuf(buf.p_oldbuf);
}

return input;
output::output(std::function<void(const std::string&)> callback)
: m_callback(std::move(callback)) {
}

output::int_type output::overflow(output::int_type c) {
std::lock_guard<std::mutex> lock(m_mutex);
// Called for each output character.
if (!traits_type::eq_int_type(c, traits_type::eof())) {
m_output.push_back(traits_type::to_char_type(c));
}
return c;
}

throw std::runtime_error("This frontend does not support input requests");
std::streamsize output::xsputn(const char* s, std::streamsize count) {
std::lock_guard<std::mutex> lock(m_mutex);
// Called for a string of characters.
m_output.append(s, count);
return count;
}

output::int_type output::sync() {
std::lock_guard<std::mutex> lock(m_mutex);
// Called in case of flush.
if (!m_output.empty()) {
m_callback(m_output);
m_output.clear();
}
return 0;
}

} // namespace xoctave
36 changes: 32 additions & 4 deletions src/input.hpp → src/io.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,23 @@
* along with xeus-octave. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef INPUT_H
#define INPUT_H
#ifndef IO_H
#define IO_H

#include <mutex>
#include <ostream>
#include <streambuf>

#include "octave/cmd-edit.h"

namespace xoctave {

class input : public octave::command_editor {
public:
static void set_command_editor(octave::command_editor &n);
static void reset_command_editor();
input(std::function<std::string(const std::string &)> callback);

static void override(input &n);
static void restore();

std::string do_readline(const std::string &prompt, bool &) override;
void do_set_input_stream(FILE *) override {}
Expand All @@ -43,6 +49,28 @@ class input : public octave::command_editor {
void do_insert_text(const std::string &) override {}
void do_newline(void) override {}
void do_accept_line(void) override {}

private:
std::function<std::string(const std::string &)> m_callback;
};

class output : public std::streambuf {
public:
output(std::function<void(const std::string &)> callback);

static void override(std::ostream &, output &);
static void restore(std::ostream &, output &);

protected:
int_type overflow(int_type c) override;
std::streamsize xsputn(const char *s, std::streamsize count) override;
int_type sync() override;

std::function<void(const std::string &)> m_callback;
std::string m_output;
std::mutex m_mutex;

std::streambuf *p_oldbuf;
};

} // namespace xoctave
Expand Down
67 changes: 28 additions & 39 deletions src/xoctave_interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
#include <vector>

#include "config.h"
#include "input.hpp"
#include "io.hpp"
#include "toolkits/notebook.hpp"
#include "toolkits/plotly.hpp"
#include "xeus/xinterpreter.hpp"
Expand All @@ -60,31 +60,6 @@ using namespace octave;

namespace xoctave {

void xoctave_interpreter::do_print_output(bool drawnow) {
bool draw = drawnow && (buf_stderr.str().length() ||
buf_stdout.str().length());

// Print output if necessary
if (!buf_stderr.str().empty()) {
publish_stream("stderr", buf_stderr.str());

// Clear stream
buf_stderr.str("");
buf_stderr.clear();
}

if (!buf_stdout.str().empty()) {
publish_stream("stdout", buf_stdout.str());

// Clear stream
buf_stdout.str("");
buf_stdout.clear();
}

if (draw)
octave::feval("drawnow");
}

void xoctave_interpreter::publish_stream(const std::string& name, const std::string& text) {
if (!m_silent)
xinterpreter::publish_stream(name, text);
Expand Down Expand Up @@ -112,6 +87,24 @@ void xoctave_interpreter::publish_execution_error(const std::string& ename,
xinterpreter::publish_execution_error(ename, evalue, trace_back);
}

std::string xoctave_interpreter::blocking_input_request(const std::string& prompt, bool password) {
if (m_allow_stdin) {
// Register the input handler
std::string value;
register_input_handler([&value](const std::string& v) { value = v; });

// Send the input request
input_request(prompt, password);

// Remove input handler
register_input_handler(nullptr);

return value;
}

throw std::runtime_error("This frontend does not support input requests");
}

nl::json xoctave_interpreter::execute_request_impl(int execution_counter,
const std::string& code,
bool silent,
Expand Down Expand Up @@ -160,35 +153,30 @@ nl::json xoctave_interpreter::execute_request_impl(int execution_counter,

if (stmt_list) {
interpreter.get_evaluator().eval(stmt_list, false);
do_print_output();
} else if (str_parser.at_end_of_input()) {
exit_status = EOF;
break;
}
}
} catch (const interrupt_exception&) {
interpreter.recover_from_exception();
do_print_output();
publish_execution_error("Interrupt exception", "Kernel was interrupted", std::vector<std::string>());
result["status"] = "error";
} catch (const index_exception& e) {
error = e.message();
error += "\n" + e.stack_trace();
interpreter.recover_from_exception();
do_print_output(false);
publish_execution_error("Index exception", error, std::vector<std::string>());
result["status"] = "error";
} catch (const execution_exception& ee) {
error = ee.message();
error += "\n" + ee.stack_trace();
interpreter.get_error_system().save_exception(ee);
interpreter.recover_from_exception();
do_print_output(false);
publish_execution_error("Execution exception", error, std::vector<std::string>());
result["status"] = "error";
} catch (const std::bad_alloc&) {
interpreter.recover_from_exception();
do_print_output(false);
publish_execution_error("Out of memory", "Trying to return to prompt", std::vector<std::string>());
result["status"] = "error";
}
Expand Down Expand Up @@ -229,8 +217,10 @@ void xoctave_interpreter::configure_impl() {
octave::feval("graphics_toolkit", ovl("plotly"));
#endif

// Override the default input system
input::set_command_editor(input_handler);
// Override the default io system
input::override(m_stdin);
output::override(std::cout, m_stdout);
output::override(std::cerr, m_stderr);

// Create the xoctave package
auto xoctave_package = interpreter.get_cdef_manager().make_package("xoctave");
Expand All @@ -240,10 +230,6 @@ void xoctave_interpreter::configure_impl() {

// Register the xoctave package
interpreter.get_cdef_manager().register_package(xoctave_package);

// Redirect output to string
std::cout.rdbuf(buf_stdout.rdbuf());
std::cerr.rdbuf(buf_stderr.rdbuf());
}

nl::json xoctave_interpreter::complete_request_impl(const std::string& code,
Expand Down Expand Up @@ -333,8 +319,11 @@ nl::json xoctave_interpreter::kernel_info_request_impl() {
}

void xoctave_interpreter::shutdown_request_impl() {
// Recover the old input system before shutting down the interpreter
input::reset_command_editor();
// Recover the old io system before shutting down the interpreter
input::restore();
output::restore(std::cout, m_stdout);
output::restore(std::cerr, m_stderr);

interpreter.shutdown();

#ifndef NDEBUG
Expand Down
13 changes: 6 additions & 7 deletions src/xoctave_interpreter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,19 @@
#include <octave/interpreter.h>
#include <octave/oct-stream.h>

#include <functional>
#include <nlohmann/json.hpp>
#include <sstream>
#include <xeus/xinterpreter.hpp>

#include "input.hpp"
#include "io.hpp"

using nlohmann::json;
using xeus::xinterpreter;

namespace xoctave {

class xoctave_interpreter : public xinterpreter {
friend input;

private:
octave::interpreter interpreter;

Expand Down Expand Up @@ -64,22 +63,22 @@ class xoctave_interpreter : public xinterpreter {
void shutdown_request_impl() override;

public:
void do_print_output(bool drawnow = true);

void publish_stream(const std::string& name, const std::string& text);
void display_data(json data, json metadata = json::object(), json transient = json::object());
void update_display_data(json data, json metadata = json::object(), json transient = json::object());
void publish_execution_result(int execution_count, nl::json data, nl::json metadata);
void publish_execution_error(const std::string& ename,
const std::string& evalue,
const std::vector<std::string>& trace_back);
std::string blocking_input_request(const std::string& prompt, bool password);

private:
std::string get_symbol(const std::string& code, int cursor_pos) const;
json get_help_for_symbol(const std::string& symbol);

std::stringstream buf_stdout, buf_stderr;
input input_handler;
output m_stdout{std::bind(&xoctave_interpreter::publish_stream, this, "stdout", std::placeholders::_1)};
output m_stderr{std::bind(&xoctave_interpreter::publish_stream, this, "stderr", std::placeholders::_1)};
input m_stdin{std::bind(&xoctave_interpreter::blocking_input_request, this, std::placeholders::_1, false)};

bool m_silent, m_allow_stdin;
};
Expand Down

0 comments on commit bf8d13e

Please sign in to comment.