diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..0cba2e68 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "iostream": "cpp" + } +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index bacd6e7f..64710e14 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -177,6 +177,7 @@ else() configure_native_kernel("/share/jupyter/kernels/xcpp17/") configure_native_kernel("/share/jupyter/kernels/xcpp20/") configure_native_kernel("/share/jupyter/kernels/xcpp23/") + configure_native_kernel("/share/jupyter/kernels/xcpp17-debugger/") endif() # Source files @@ -193,6 +194,8 @@ set(XEUS_CPP_HEADERS include/xeus-cpp/xmagics.hpp include/xeus-cpp/xoptions.hpp include/xeus-cpp/xpreamble.hpp + include/xeus-cpp/xdebugger.hpp + include/xeus-cpp/xshared_memory.hpp #src/xinspect.hpp #src/xsystem.hpp #src/xparser.hpp @@ -207,6 +210,10 @@ set(XEUS_CPP_SRC src/xparser.cpp src/xutils.cpp src/xmagics/os.cpp + src/xdebugger.cpp + src/xinternal_utils.cpp + src/xdebuglldb_client.cpp + src/xcppinterop_process.cpp ) if(NOT EMSCRIPTEN) @@ -458,11 +465,13 @@ include(CMakePackageConfigHelpers) set(XEUS_CPP_CMAKECONFIG_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" CACHE STRING "install path for xeus-cppConfig.cmake") -install(DIRECTORY ${XCPP_TAGFILES_DIR} - DESTINATION ${XEUS_CPP_DATA_DIR}) +if(NOT EMSCRIPTEN) + install(DIRECTORY ${XCPP_TAGFILES_DIR} + DESTINATION ${XEUS_CPP_DATA_DIR}) -install(DIRECTORY ${XCPP_TAGCONFS_DIR} - DESTINATION ${XEUS_CPP_CONF_DIR}) + install(DIRECTORY ${XCPP_TAGCONFS_DIR} + DESTINATION ${XEUS_CPP_CONF_DIR}) +endif() # Install xeus-cpp and xeus-cpp-static if (XEUS_CPP_BUILD_SHARED) @@ -503,6 +512,65 @@ if(XEUS_CPP_BUILD_EXECUTABLE OR EMSCRIPTEN) endif () endif () +# cppinterop_process executable +# ============================= + +if (XEUS_CPP_BUILD_EXECUTABLE AND NOT EMSCRIPTEN) + # Define source files for cppinterop_process + set(CPPINTEROP_PROCESS_SRC + src/xcppinterop_process.cpp # You'll need to create this file + # Add any other source files needed for the process + ${XEUS_CPP_HEADERS} + ) + + add_executable(cppinterop_process ${CPPINTEROP_PROCESS_SRC}) + + # Add xeus-cpp include directory + target_include_directories(cppinterop_process + PRIVATE + ${XEUS_CPP_INCLUDE_DIR} + ) + + # Set common compile options + xeus_cpp_set_common_options(cppinterop_process) + + target_compile_options(cppinterop_process PRIVATE + -gdwarf-4 # Generate DWARF-4 debug information + -O0 # Disable optimization + ) + + # Link with necessary libraries + target_link_libraries(cppinterop_process PRIVATE + clangCppInterOp + ${CMAKE_THREAD_LIBS_INIT} + ) + + # Add any additional libraries needed for shared memory, etc. + if(CMAKE_DL_LIBS) + target_link_libraries(cppinterop_process PRIVATE ${CMAKE_DL_LIBS} util) + endif() + + # Set target properties + set_target_properties(cppinterop_process PROPERTIES + INSTALL_RPATH_USE_LINK_PATH TRUE + ) + + if (APPLE) + set_target_properties(cppinterop_process PROPERTIES + MACOSX_RPATH ON + ) + else() + set_target_properties(cppinterop_process PROPERTIES + BUILD_WITH_INSTALL_RPATH 1 + SKIP_BUILD_RPATH FALSE + ) + endif() + + # Install the executable + install(TARGETS cppinterop_process + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +endif() + # Configure 'xeus-cppConfig.cmake' for a build tree set(XEUS_CPP_CONFIG_CODE "####### Expanded from \@XEUS_CPP_CONFIG_CODE\@ #######\n") set(XEUS_CPP_CONFIG_CODE "${XEUS_CPP_CONFIG_CODE}set(CMAKE_MODULE_PATH \"${CMAKE_CURRENT_SOURCE_DIR}/cmake;\${CMAKE_MODULE_PATH}\")\n") diff --git a/include/xeus-cpp/xdebugger.hpp b/include/xeus-cpp/xdebugger.hpp new file mode 100644 index 00000000..7fd8311f --- /dev/null +++ b/include/xeus-cpp/xdebugger.hpp @@ -0,0 +1,86 @@ +/************************************************************************************ + * Copyright (c) 2023, xeus-cpp contributors * + * Copyright (c) 2023, Johan Mabille, Loic Gouarin, Sylvain Corlay, Wolf Vollprecht * + * * + * Distributed under the terms of the BSD 3-Clause License. * + * * + * The full license is in the file LICENSE, distributed with this software. * + ************************************************************************************/ + +#ifndef XEUS_CPP_DEBUGGER_HPP +#define XEUS_CPP_DEBUGGER_HPP + +#ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wattributes" +#endif + +#include +#include +#include +#include + +#include "nlohmann/json.hpp" +#include "xeus-zmq/xdebugger_base.hpp" +#include "xeus_cpp_config.hpp" + +namespace xcpp +{ + class xdebuglldb_client; + + class XEUS_CPP_API debugger : public xeus::xdebugger_base + { + public: + + using base_type = xeus::xdebugger_base; + + debugger(xeus::xcontext& context, + const xeus::xconfiguration& config, + const std::string& user_name, + const std::string& session_id, + const nl::json& debugger_config); + + virtual ~debugger(); + + private: + + nl::json inspect_variables_request(const nl::json& message); + nl::json stack_trace_request(const nl::json& message); + nl::json attach_request(const nl::json& message); + nl::json configuration_done_request(const nl::json& message); + + nl::json variables_request_impl(const nl::json& message) override; + + bool start_lldb(); + bool start() override; + void stop() override; + xeus::xdebugger_info get_debugger_info() const override; + std::string get_cell_temporary_file(const std::string& code) const override; + + bool connect_to_lldb_tcp(); + std::string send_dap_message(const nl::json& message); + std::string receive_dap_response(); + + xdebuglldb_client* p_debuglldb_client; + std::string m_lldb_host; + std::string m_lldb_port; + std::string m_lldbdap_port; + nl::json m_debugger_config; + bool m_is_running; + int m_tcp_socket; + bool m_tcp_connected; + }; + + XEUS_CPP_API + std::unique_ptr make_cpp_debugger(xeus::xcontext& context, + const xeus::xconfiguration& config, + const std::string& user_name, + const std::string& session_id, + const nl::json& debugger_config); +} + +#ifdef __GNUC__ + #pragma GCC diagnostic pop +#endif + +#endif \ No newline at end of file diff --git a/include/xeus-cpp/xinterpreter.hpp b/include/xeus-cpp/xinterpreter.hpp index 25eeb049..ef76a2ad 100644 --- a/include/xeus-cpp/xinterpreter.hpp +++ b/include/xeus-cpp/xinterpreter.hpp @@ -30,6 +30,7 @@ namespace nl = nlohmann; namespace xcpp { + class CppInterOpClient; class XEUS_CPP_API interpreter : public xeus::xinterpreter { public: @@ -37,6 +38,8 @@ namespace xcpp interpreter(int argc, const char* const* argv); virtual ~interpreter(); + void set_cppinterop_client(std::shared_ptr client); + void publish_stdout(const std::string&); void publish_stderr(const std::string&); @@ -62,6 +65,8 @@ namespace xcpp void shutdown_request_impl() override; + nl::json internal_request_impl(const nl::json& content) override; + nl::json get_error_reply( const std::string& ename, const std::string& evalue, @@ -85,6 +90,8 @@ namespace xcpp xoutput_buffer m_cout_buffer; xoutput_buffer m_cerr_buffer; + + std::shared_ptr m_cppinterop_client; }; } diff --git a/include/xeus-cpp/xshared_memory.hpp b/include/xeus-cpp/xshared_memory.hpp new file mode 100644 index 00000000..de4a13df --- /dev/null +++ b/include/xeus-cpp/xshared_memory.hpp @@ -0,0 +1,147 @@ +#pragma once + +#include +#include +#include +#include +#include + +struct SharedMemoryBuffer { + // FIXED: Reduced buffer sizes to fit within 64KB system limit + // Total struct size should be around 52KB, leaving room for other fields + static constexpr size_t MAX_CODE_SIZE = 16 * 1024; // 16KB for code + static constexpr size_t MAX_OUTPUT_SIZE = 16 * 1024; // 16KB for output + static constexpr size_t MAX_ERROR_SIZE = 8 * 1024; // 8KB for errors + static constexpr size_t MAX_COMPLETION_SIZE = 8 * 1024; // 8KB for completions + + enum class RequestType : uint32_t { + NONE = 0, + PROCESS_CODE, + CODE_COMPLETE, + EVALUATE, + SHUTDOWN + }; + + enum class ResponseStatus : uint32_t { + NONE = 0, + SUCCESS, + COMPILATION_ERROR, + RUNTIME_ERROR, + SYSTEM_ERROR + }; + + std::atomic request_ready{false}; + std::atomic response_ready{false}; + std::atomic request_type{RequestType::NONE}; + std::atomic response_status{ResponseStatus::NONE}; + + char code_buffer[MAX_CODE_SIZE]; + uint32_t code_length; + int cursor_pos; + + char output_buffer[MAX_OUTPUT_SIZE]; + char error_buffer[MAX_ERROR_SIZE]; + uint32_t output_length; + uint32_t error_length; + bool compilation_result; + int64_t evaluation_result; + + char completion_buffer[MAX_COMPLETION_SIZE]; // Use separate size constant + uint32_t completion_length; + + void reset() { + request_ready.store(false, std::memory_order_relaxed); + response_ready.store(false, std::memory_order_relaxed); + request_type.store(RequestType::NONE, std::memory_order_relaxed); + response_status.store(ResponseStatus::NONE, std::memory_order_relaxed); + code_length = 0; + output_length = 0; + error_length = 0; + completion_length = 0; + cursor_pos = 0; + compilation_result = false; + evaluation_result = 0; + + memset(code_buffer, 0, MAX_CODE_SIZE); + memset(output_buffer, 0, MAX_OUTPUT_SIZE); + memset(error_buffer, 0, MAX_ERROR_SIZE); + memset(completion_buffer, 0, MAX_COMPLETION_SIZE); + } + + void set_code(const std::string& code) { + code_length = std::min(code.length(), MAX_CODE_SIZE - 1); + memcpy(code_buffer, code.c_str(), code_length); + code_buffer[code_length] = '\0'; + } + + std::string get_code() const { + return std::string(code_buffer, code_length); + } + + void set_output(const std::string& output) { + output_length = std::min(output.length(), MAX_OUTPUT_SIZE - 1); + memcpy(output_buffer, output.c_str(), output_length); + output_buffer[output_length] = '\0'; + } + + std::string get_output() const { + return std::string(output_buffer, output_length); + } + + void set_error(const std::string& error) { + error_length = std::min(error.length(), MAX_ERROR_SIZE - 1); + memcpy(error_buffer, error.c_str(), error_length); + error_buffer[error_length] = '\0'; + } + + std::string get_error() const { + return std::string(error_buffer, error_length); + } + + void set_completions(const std::vector& completions) { + std::string combined; + for (const auto& comp : completions) { + if (!combined.empty()) combined += "\n"; + combined += comp; + } + completion_length = std::min(combined.length(), MAX_COMPLETION_SIZE - 1); + memcpy(completion_buffer, combined.c_str(), completion_length); + completion_buffer[completion_length] = '\0'; + } + + std::vector get_completions() const { + std::vector result; + std::string data(completion_buffer, completion_length); + std::istringstream iss(data); + std::string line; + while (std::getline(iss, line)) { + result.push_back(line); + } + return result; + } + + // Helper function to get the total size of this struct + static constexpr size_t total_size() { + return sizeof(SharedMemoryBuffer); + } + + // Helper function to check if a given size can accommodate this struct + static bool fits_in_size(size_t available_size) { + return available_size >= sizeof(SharedMemoryBuffer); + } +}; + +// Static assertion to ensure the struct fits in reasonable shared memory limits +static_assert(sizeof(SharedMemoryBuffer) <= 65536, + "SharedMemoryBuffer too large for typical shared memory limits"); + +// Print size information for debugging +inline void print_buffer_size_info() { + std::cout << "SharedMemoryBuffer size breakdown:" << std::endl; + std::cout << " Code buffer: " << SharedMemoryBuffer::MAX_CODE_SIZE << " bytes" << std::endl; + std::cout << " Output buffer: " << SharedMemoryBuffer::MAX_OUTPUT_SIZE << " bytes" << std::endl; + std::cout << " Error buffer: " << SharedMemoryBuffer::MAX_ERROR_SIZE << " bytes" << std::endl; + std::cout << " Completion buffer: " << SharedMemoryBuffer::MAX_COMPLETION_SIZE << " bytes" << std::endl; + std::cout << " Total struct size: " << sizeof(SharedMemoryBuffer) << " bytes" << std::endl; + std::cout << " Fits in 64KB: " << (sizeof(SharedMemoryBuffer) <= 65536 ? "YES" : "NO") << std::endl; +} \ No newline at end of file diff --git a/share/jupyter/kernels/xcpp17-debugger/kernel.json.in b/share/jupyter/kernels/xcpp17-debugger/kernel.json.in new file mode 100644 index 00000000..1f1d839e --- /dev/null +++ b/share/jupyter/kernels/xcpp17-debugger/kernel.json.in @@ -0,0 +1,20 @@ +{ + "display_name": "C++17-Debugger", + "env": { + "PATH":"@XEUS_CPP_PATH@", + "LD_LIBRARY_PATH":"@XEUS_CPP_LD_LIBRARY_PATH@" + }, + "argv": [ + "@XEUS_CPP_KERNELSPEC_PATH@xcpp", + "-f", + "{connection_file}", + "-gdwarf-4", + "-O0", + "-resource-dir", "@XEUS_CPP_RESOURCE_DIR@", + "-I", "@XEUS_CPP_INCLUDE_DIR@", + "-std=c++17" + ], + "language": "cpp", + "metadata": {"debugger": true + } +} diff --git a/share/jupyter/kernels/xcpp17-debugger/logo-32x32.png b/share/jupyter/kernels/xcpp17-debugger/logo-32x32.png new file mode 100644 index 00000000..c09c4585 Binary files /dev/null and b/share/jupyter/kernels/xcpp17-debugger/logo-32x32.png differ diff --git a/share/jupyter/kernels/xcpp17-debugger/logo-64x64.png b/share/jupyter/kernels/xcpp17-debugger/logo-64x64.png new file mode 100644 index 00000000..396c2446 Binary files /dev/null and b/share/jupyter/kernels/xcpp17-debugger/logo-64x64.png differ diff --git a/share/jupyter/kernels/xcpp17-debugger/logo-svg.svg b/share/jupyter/kernels/xcpp17-debugger/logo-svg.svg new file mode 100644 index 00000000..5e117077 --- /dev/null +++ b/share/jupyter/kernels/xcpp17-debugger/logo-svg.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + diff --git a/share/jupyter/kernels/xcpp17-omp/kernel.json.in b/share/jupyter/kernels/xcpp17-omp/kernel.json.in index f74379f1..6a3443b8 100644 --- a/share/jupyter/kernels/xcpp17-omp/kernel.json.in +++ b/share/jupyter/kernels/xcpp17-omp/kernel.json.in @@ -13,6 +13,6 @@ "-std=c++17"@XEUS_CPP_OMP@ ], "language": "cpp", - "metadata": {"debugger": false + "metadata": {"debugger": true } } diff --git a/share/jupyter/kernels/xcpp17/kernel.json.in b/share/jupyter/kernels/xcpp17/kernel.json.in index c09f6836..10b5b5d6 100644 --- a/share/jupyter/kernels/xcpp17/kernel.json.in +++ b/share/jupyter/kernels/xcpp17/kernel.json.in @@ -13,6 +13,6 @@ "-std=c++17" ], "language": "cpp", - "metadata": {"debugger": false + "metadata": {"debugger": true } } diff --git a/share/jupyter/kernels/xcpp17/wasm_kernel.json.in b/share/jupyter/kernels/xcpp17/wasm_kernel.json.in index a6be99dd..c9fe5b6f 100644 --- a/share/jupyter/kernels/xcpp17/wasm_kernel.json.in +++ b/share/jupyter/kernels/xcpp17/wasm_kernel.json.in @@ -7,7 +7,7 @@ ], "language": "cpp", "metadata": { - "debugger": false, + "debugger": true, "shared": { "libclangCppInterOp.so": "lib/libclangCppInterOp.so" } diff --git a/src/main.cpp b/src/main.cpp index 00d52d28..9277ef16 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,16 +19,20 @@ #include #endif +#include "nlohmann/json.hpp" #include "xeus/xhelper.hpp" -#include -#include - +#include "xeus/xkernel.hpp" +#include "xeus/xkernel_configuration.hpp" #include "xeus-zmq/xzmq_context.hpp" -#include - +#include "xeus-zmq/xserver_zmq.hpp" #include "xeus-cpp/xeus_cpp_config.hpp" #include "xeus-cpp/xinterpreter.hpp" #include "xeus-cpp/xutils.hpp" +#include "xeus-cpp/xdebugger.hpp" + +#include "xcppinterop_client.hpp" + +namespace nl = nlohmann; int main(int argc, char* argv[]) { @@ -58,10 +62,26 @@ int main(int argc, char* argv[]) #endif signal(SIGINT, xcpp::stop_handler); + // Debugger configuration for LLDB-DAP + nl::json debugger_config; + debugger_config["lldb"]["initCommands"] = { + "settings set plugin.jit-loader.gdb.enable on" + }; + std::string file_name = xeus::extract_filename(argc, argv); auto interpreter = std::make_unique(argc, argv); std::unique_ptr context = xeus::make_zmq_context(); + auto cppinterop_client = std::make_shared(); + if (!cppinterop_client->initialize()) + { + std::cerr << "Failed to initialize CppInterOpClient" << std::endl; + return 1; + } + + interpreter->set_cppinterop_client(cppinterop_client); + + if (!file_name.empty()) { xeus::xconfiguration config = xeus::load_configuration(file_name); @@ -77,13 +97,14 @@ int main(int argc, char* argv[]) xeus::make_console_logger( xeus::xlogger::msg_type, xeus::make_file_logger(xeus::xlogger::content, "xeus.log") - ) + ), + xcpp::make_cpp_debugger, + debugger_config ); std::clog << "Starting xcpp kernel...\n\n" "If you want to connect to this kernel from an other client, you can use" - " the " - + file_name + " file." + " the " + file_name + " file." << std::endl; kernel.start(); @@ -99,7 +120,9 @@ int main(int argc, char* argv[]) xeus::make_console_logger( xeus::xlogger::msg_type, xeus::make_file_logger(xeus::xlogger::content, "xeus.log") - ) + ), + xcpp::make_cpp_debugger, + debugger_config ); std::cout << "Getting config" << std::endl; @@ -109,37 +132,20 @@ int main(int argc, char* argv[]) " and paste the following content inside of a `kernel.json` file. And then run for example:\n\n" "# jupyter console --existing kernel.json\n\n" "kernel.json\n```\n{\n" - " \"transport\": \"" - + config.m_transport - + "\",\n" - " \"ip\": \"" - + config.m_ip - + "\",\n" - " \"control_port\": " - + config.m_control_port - + ",\n" - " \"shell_port\": " - + config.m_shell_port - + ",\n" - " \"stdin_port\": " - + config.m_stdin_port - + ",\n" - " \"iopub_port\": " - + config.m_iopub_port - + ",\n" - " \"hb_port\": " - + config.m_hb_port - + ",\n" - " \"signature_scheme\": \"" - + config.m_signature_scheme - + "\",\n" - " \"key\": \"" - + config.m_key - + "\"\n" - "}\n```\n"; + " \"transport\": \"" + config.m_transport + "\",\n" + " \"ip\": \"" + config.m_ip + "\",\n" + " \"control_port\": " + config.m_control_port + ",\n" + " \"shell_port\": " + config.m_shell_port + ",\n" + " \"stdin_port\": " + config.m_stdin_port + ",\n" + " \"iopub_port\": " + config.m_iopub_port + ",\n" + " \"hb_port\": " + config.m_hb_port + ",\n" + " \"signature_scheme\": \"" + config.m_signature_scheme + "\",\n" + " \"key\": \"" + config.m_key + "\"\n" + "}\n```" + << std::endl; kernel.start(); } return 0; -} +} \ No newline at end of file diff --git a/src/xcppinterop_client.hpp b/src/xcppinterop_client.hpp new file mode 100644 index 00000000..158bbbe6 --- /dev/null +++ b/src/xcppinterop_client.hpp @@ -0,0 +1,317 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using Args = std::vector; + + +#include "xeus-cpp/xshared_memory.hpp" + +namespace xcpp +{ + class CppInterOpClient + { + private: + + SharedMemoryBuffer* m_shared_buffer; + int m_shm_fd; + std::string m_shm_name; + pid_t m_child_pid; + bool m_initialized; + + public: + + CppInterOpClient() + : m_shared_buffer(nullptr) + , m_shm_fd(-1) + , m_child_pid(-1) + , m_initialized(false) + { + // Generate unique shared memory name + m_shm_name = "/xcpp_shm_" + std::to_string(getpid()); + } + + ~CppInterOpClient() + { + std::cerr << "[~CppInterOpClient] Cleaning up...\n"; + cleanup(); + } + + bool initialize() + { + // Create shared memory + shm_unlink(m_shm_name.c_str()); + m_shm_fd = shm_open(m_shm_name.c_str(), O_CREAT | O_RDWR | O_EXCL, 0666); + if (m_shm_fd == -1) + { + std::cerr << "Failed to create shared memory" << std::endl; + return false; + } + + // Set size + if (ftruncate(m_shm_fd, sizeof(SharedMemoryBuffer)) == -1) + { + std::cerr << "Failed to set shared memory size" << std::endl; + return false; + } + + // Map shared memory + m_shared_buffer = static_cast( + mmap(nullptr, sizeof(SharedMemoryBuffer), PROT_READ | PROT_WRITE, MAP_SHARED, m_shm_fd, 0) + ); + + if (m_shared_buffer == MAP_FAILED) + { + std::cerr << "Failed to map shared memory" << std::endl; + return false; + } + + // Initialize shared buffer + m_shared_buffer->reset(); + + // Fork and exec the CppInterOp process, redirecting child stdout/stderr to parent's + int pipefd[2]; + if (pipe(pipefd) == -1) + { + std::cerr << "Failed to create pipe for child logs" << std::endl; + return false; + } + + // ******************** + // This code block can be simplified more. Currently, it also fetches the logs of the child process. + m_child_pid = fork(); + if (m_child_pid == -1) + { + std::cerr << "Failed to fork CppInterOp process" << std::endl; + close(pipefd[0]); + close(pipefd[1]); + return false; + } + + if (m_child_pid == 0) + { + pid_t parent_pid = getppid(); + // Start monitoring thread + std::thread([]() { + while (true) { + if (getppid() == 1) { // Parent died, we're now child of init + exit(1); + } + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + }).detach(); + + close(pipefd[0]); + dup2(pipefd[1], STDOUT_FILENO); + dup2(pipefd[1], STDERR_FILENO); + close(pipefd[1]); + + execl("./cppinterop_process", "cppinterop_process", m_shm_name.c_str(), nullptr); + std::cerr << "Failed to exec CppInterOp process" << std::endl; + exit(1); + } + else + { + close(pipefd[1]); + std::thread([read_fd = pipefd[0]]() { + char buffer[256]; + ssize_t n; + while ((n = read(read_fd, buffer, sizeof(buffer) - 1)) > 0) + { + buffer[n] = '\0'; + // std::cout << "[CppInterOp child] " << buffer; + } + close(read_fd); + }).detach(); + } + // ******************** + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + int status; + pid_t result = waitpid(m_child_pid, &status, WNOHANG); + if (result != 0) + { + std::cerr << "CppInterOp process failed to start" << std::endl; + return false; + } + + m_initialized = true; + std::clog << "CppInterOp client initialized with child PID: " << m_child_pid << std::endl; + return true; + } + + bool processCode(const std::string& code, std::string& output, std::string& error) + { + if (!m_initialized) + { + return false; + } + + m_shared_buffer->reset(); + m_shared_buffer->set_code(code); + m_shared_buffer->request_type = SharedMemoryBuffer::RequestType::PROCESS_CODE; + m_shared_buffer->request_ready.store(true, std::memory_order_release); + + // Wait for response + if (!waitForResponse()) + { + return false; + } + + output = m_shared_buffer->get_output(); + error = m_shared_buffer->get_error(); + + return m_shared_buffer->response_status.load() == SharedMemoryBuffer::ResponseStatus::SUCCESS; + } + + bool codeComplete(const std::string& code, int cursor_pos, std::vector& results) + { + if (!m_initialized) + { + return false; + } + + m_shared_buffer->reset(); + m_shared_buffer->set_code(code); + m_shared_buffer->cursor_pos = cursor_pos; + m_shared_buffer->request_type = SharedMemoryBuffer::RequestType::CODE_COMPLETE; + m_shared_buffer->request_ready = true; + + // Wait for response + if (!waitForResponse()) + { + return false; + } + + results = m_shared_buffer->get_completions(); + return m_shared_buffer->response_status.load() == SharedMemoryBuffer::ResponseStatus::SUCCESS; + } + + bool evaluate(const std::string& code, int64_t& result) + { + if (!m_initialized) + { + return false; + } + + m_shared_buffer->reset(); + m_shared_buffer->set_code(code); + m_shared_buffer->request_type = SharedMemoryBuffer::RequestType::EVALUATE; + m_shared_buffer->request_ready = true; + + // Wait for response + if (!waitForResponse()) + { + return false; + } + + if (m_shared_buffer->response_status.load() == SharedMemoryBuffer::ResponseStatus::SUCCESS) + { + result = m_shared_buffer->evaluation_result; + return true; + } + + return false; + } + + void shutdown() + { + if (!m_initialized) + { + return; + } + + m_shared_buffer->request_type = SharedMemoryBuffer::RequestType::SHUTDOWN; + m_shared_buffer->request_ready = true; + + // Wait a bit for graceful shutdown + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + // Kill child process if it's still running + if (m_child_pid > 0) + { + std::cout << "Terminating CppInterOp child process with PID: " << m_child_pid << std::endl; + kill(m_child_pid, SIGTERM); + + // Wait for child to exit + int status; + waitpid(m_child_pid, &status, 0); + m_child_pid = -1; + } + } + + void cleanup() + { + if (m_child_pid > 0) { + kill(-m_child_pid, SIGKILL); // Negative PID kills process group + } + + if (m_initialized) + { + shutdown(); + } + + if (m_shared_buffer && m_shared_buffer != MAP_FAILED) + { + munmap(m_shared_buffer, sizeof(SharedMemoryBuffer)); + m_shared_buffer = nullptr; + } + + if (m_shm_fd != -1) + { + close(m_shm_fd); + shm_unlink(m_shm_name.c_str()); + m_shm_fd = -1; + } + } + + private: + + bool waitForResponse(int timeout_ms = 100000) + { + auto start = std::chrono::steady_clock::now(); + + while (!m_shared_buffer->response_ready.load()) + { + auto now = std::chrono::steady_clock::now(); + if (std::chrono::duration_cast(now - start).count() > timeout_ms) + { + std::cerr << "Timeout waiting for CppInterOp response" << std::endl; + return false; + } + + // Check if child process is still alive + int status; + pid_t result = waitpid(m_child_pid, &status, WNOHANG); + static int elapsed_ms = 0; + if (result != 0) + { + elapsed_ms += 1; + } + else + { + elapsed_ms = 0; + } + // If more than 100 seconds have passed, return false + if (elapsed_ms > 100000) + { + std::cerr << "Timeout: No response from CppInterOp process after 10 seconds" << std::endl; + return false; + } + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + elapsed_ms += 1; + } + + return true; + } + }; +}; + diff --git a/src/xcppinterop_process.cpp b/src/xcppinterop_process.cpp new file mode 100644 index 00000000..1524483e --- /dev/null +++ b/src/xcppinterop_process.cpp @@ -0,0 +1,462 @@ +// cppinterop_process.cpp +// Separate process that handles CppInterOp operations - FIXED VERSION + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "clang/Interpreter/CppInterOp.h" // from CppInterOp package +#include "xeus-cpp/xshared_memory.hpp" + +class CppInterOpProcess { +private: + void* m_interpreter; + SharedMemoryBuffer* m_shared_buffer; + int m_shm_fd; + bool m_running; + std::string m_shm_name; + size_t m_shm_size; + static std::atomic initialized; + static std::mutex init_mutex; + + // Helper function to get system shared memory limits + size_t getMaxShmSize() { + // Try to read system limits + std::ifstream shmmax("/proc/sys/kernel/shmmax"); + if (shmmax.is_open()) { + size_t max_size; + shmmax >> max_size; + shmmax.close(); + return max_size; + } + + // Fallback to a conservative size (1MB for better compatibility) + return 1024 * 1024; + } + + // Helper function to validate and adjust shared memory size + size_t validateShmSize(size_t requested_size) { + size_t max_size = getMaxShmSize(); + size_t min_size = sizeof(SharedMemoryBuffer); + + std::clog << "Requested SHM size: " << requested_size << " bytes" << std::endl; + std::clog << "System max SHM size: " << max_size << " bytes" << std::endl; + std::clog << "Minimum required size: " << min_size << " bytes" << std::endl; + + if (requested_size > max_size) { + std::clog << "Warning: Requested size exceeds system limit, using " << max_size << " bytes" << std::endl; + return max_size; + } + + if (requested_size < min_size) { + std::clog << "Warning: Requested size too small, using minimum " << min_size << " bytes" << std::endl; + return min_size; + } + + return requested_size; + } + + // Fix corrupted include paths + std::vector sanitizeIncludePaths(const std::vector& paths) { + std::vector sanitized; + + for (size_t i = 0; i < paths.size(); ++i) { + const std::string& path = paths[i]; + + std::clog << "Processing path[" << i << "]: '" << path << "' (length: " << path.length() << ")" << std::endl; + + // Skip empty or obviously corrupted paths + if (path.empty() || path.length() < 3) { + std::clog << "Skipping invalid path (too short): '" << path << "'" << std::endl; + continue; + } + + // Check if path contains non-printable characters or null bytes + bool has_invalid_chars = false; + for (size_t j = 0; j < path.length(); ++j) { + char c = path[j]; + if (c == '\0' || (c > 0 && c < 32 && c != '\n' && c != '\t')) { + has_invalid_chars = true; + std::clog << "Found invalid character at position " << j << ": 0x" << std::hex << (int)(unsigned char)c << std::dec << std::endl; + break; + } + } + + if (has_invalid_chars) { + std::clog << "Skipping path with invalid characters: '" << path << "'" << std::endl; + continue; + } + + // Additional check: path should start with '/' on Unix systems + if (path[0] != '/') { + std::clog << "Skipping relative path: '" << path << "'" << std::endl; + continue; + } + + // Check if directory actually exists + struct stat st; + if (stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) { + sanitized.push_back(path); + std::clog << "Added valid include path: " << path << std::endl; + } else { + std::clog << "Skipping non-existent path: " << path << " (error: " << strerror(errno) << ")" << std::endl; + } + } + + return sanitized; + } + + // Alternative: try to use minimal system includes if detection fails + std::vector getMinimalSystemIncludes() { + std::vector minimal_includes; + + // Common macOS system include paths + std::vector candidates = { + "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1", + "/Library/Developer/CommandLineTools/usr/lib/clang/17/include", + "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include", + "/usr/include", + "/usr/local/include" + }; + + for (const std::string& path : candidates) { + struct stat st; + if (stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) { + minimal_includes.push_back(path); + std::clog << "Added minimal system include: " << path << std::endl; + } + } + + return minimal_includes; + } + +public: + CppInterOpProcess(const std::string& shm_name, size_t shm_size = sizeof(SharedMemoryBuffer)) + : m_interpreter(nullptr), m_shared_buffer(nullptr), + m_shm_fd(-1), m_running(true), m_shm_name(shm_name) { + m_shm_size = validateShmSize(shm_size); + } + + ~CppInterOpProcess() { + cleanup(); + } + + bool initialize() { + // Create shared memory + m_shm_fd = -1; + for (int i = 0; i < 50; ++i) { + m_shm_fd = shm_open(m_shm_name.c_str(), O_RDWR, 0666); + if (m_shm_fd != -1) break; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + if(m_shm_fd == -1) { + std::cerr << "Failed to open shared memory: " << strerror(errno) << std::endl; + return false; + } + + // Map shared memory + m_shared_buffer = static_cast( + mmap(nullptr, m_shm_size, + PROT_READ | PROT_WRITE, MAP_SHARED, m_shm_fd, 0)); + + if (m_shared_buffer == MAP_FAILED) { + std::cerr << "Failed to map shared memory: " << strerror(errno) << std::endl; + return false; + } + + std::clog << "Successfully mapped shared memory at " << m_shared_buffer << std::endl; + + // Initialize shared buffer + m_shared_buffer->reset(); + + // Initialize CppInterOp interpreter + if (!initializeInterpreter()) { + std::cerr << "Failed to initialize CppInterOp interpreter" << std::endl; + cleanup(); + return false; + } + + std::clog << "CppInterOp process initialized successfully at shm_name: " << m_shm_name << std::endl; + initialized.store(true); + return true; + } + + void run() { + std::clog << "CppInterOp process started, waiting for requests..." << std::endl; + + while (m_running) { + // Check for new requests + // std::clog << m_shared_buffer->request_ready.load() << std::endl; + if (m_shared_buffer->request_ready.load(std::memory_order_acquire)) { + processRequest(); + m_shared_buffer->request_ready.store(false); + m_shared_buffer->response_ready.store(true); + } + + // Small delay to prevent busy waiting + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + + std::clog << "CppInterOp process shutting down..." << std::endl; + } + + // Get current shared memory size + size_t getSharedMemorySize() const { + return m_shm_size; + } + +private: +bool initializeInterpreter() { + try { + // Simplified ClangArgs construction + std::vector ClangArgs; + std::vector args_storage; + + ClangArgs.push_back("-g"); + ClangArgs.push_back("-O0"); + + // Add resource directory + std::string resource_dir = Cpp::DetectResourceDir(); + if (!resource_dir.empty()) { + ClangArgs.push_back("-resource-dir"); + ClangArgs.push_back(resource_dir.c_str()); + std::clog << "Using resource directory: " << resource_dir << std::endl; + } else { + std::clog << "Failed to detect resource-dir" << std::endl; + } + + // Detect and sanitize system includes + std::vector CxxSystemIncludes; + Cpp::DetectSystemCompilerIncludePaths(CxxSystemIncludes); + std::clog << "Detected " << CxxSystemIncludes.size() << " system include paths (before sanitization)" << std::endl; + + CxxSystemIncludes = sanitizeIncludePaths(CxxSystemIncludes); + std::clog << "Using " << CxxSystemIncludes.size() << " valid system include paths (after sanitization)" << std::endl; + + // Add -isystem for each include path + for (const std::string& include : CxxSystemIncludes) { + ClangArgs.push_back("-isystem"); + ClangArgs.push_back(include.c_str()); + std::clog << "Added: -isystem " << include << std::endl; + } + + for (size_t i = 0; i < ClangArgs.size(); ++i) { + std::clog << " Arg[" << i << "]: '" << (ClangArgs[i] ? ClangArgs[i] : "") << "'" << std::endl; + } + + m_interpreter = Cpp::CreateInterpreter(ClangArgs); + + if (m_interpreter) { + std::clog << "CppInterOp interpreter created successfully" << std::endl; + return true; + } else { + std::cerr << "CppInterOp interpreter creation returned null" << std::endl; + return false; + } + } catch (const std::exception& e) { + std::cerr << "Exception during interpreter initialization: " << e.what() << std::endl; + return false; + } + } + + void processRequest() { + auto request_type = m_shared_buffer->request_type.load(); + std::clog << "Processing request type: " << static_cast(request_type) << std::endl; + + try { + switch (request_type) { + case SharedMemoryBuffer::RequestType::PROCESS_CODE: + processCode(); + break; + + case SharedMemoryBuffer::RequestType::CODE_COMPLETE: + processCodeCompletion(); + break; + + case SharedMemoryBuffer::RequestType::EVALUATE: + processEvaluation(); + break; + + case SharedMemoryBuffer::RequestType::SHUTDOWN: + m_running = false; + m_shared_buffer->response_status = SharedMemoryBuffer::ResponseStatus::SUCCESS; + break; + + default: + m_shared_buffer->response_status = SharedMemoryBuffer::ResponseStatus::SYSTEM_ERROR; + m_shared_buffer->set_error("Unknown request type"); + break; + } + } + catch (const std::exception& e) { + m_shared_buffer->response_status = SharedMemoryBuffer::ResponseStatus::SYSTEM_ERROR; + m_shared_buffer->set_error(std::string("Exception: ") + e.what()); + } + } + + void processCode() { + std::string code = m_shared_buffer->get_code(); + + std::clog << "Processing code in CppInterOpProcess: " << code << std::endl; + + if (!m_interpreter) { + m_shared_buffer->response_status = SharedMemoryBuffer::ResponseStatus::SYSTEM_ERROR; + m_shared_buffer->set_error("Interpreter not initialized"); + return; + } + + try { + // Capture streams similar to your original StreamRedirectRAII + Cpp::BeginStdStreamCapture(Cpp::kStdErr); // stderr + Cpp::BeginStdStreamCapture(Cpp::kStdOut); // stdout + + bool compilation_result = Cpp::Process(code.c_str()); + + std::string output = Cpp::EndStdStreamCapture(); // stdout + std::string error = Cpp::EndStdStreamCapture(); // stderr + + m_shared_buffer->compilation_result = compilation_result; + m_shared_buffer->set_output(output); + m_shared_buffer->set_error(error); + + if (!compilation_result) { + m_shared_buffer->response_status = SharedMemoryBuffer::ResponseStatus::COMPILATION_ERROR; + } else { + m_shared_buffer->response_status = SharedMemoryBuffer::ResponseStatus::SUCCESS; + } + } catch (const std::exception& e) { + m_shared_buffer->response_status = SharedMemoryBuffer::ResponseStatus::SYSTEM_ERROR; + m_shared_buffer->set_error(std::string("Code processing exception: ") + e.what()); + } + } + + void processCodeCompletion() { + if (!m_interpreter) { + m_shared_buffer->response_status = SharedMemoryBuffer::ResponseStatus::SYSTEM_ERROR; + m_shared_buffer->set_error("Interpreter not initialized"); + return; + } + + try { + std::string code = m_shared_buffer->get_code(); + int cursor_pos = m_shared_buffer->cursor_pos; + + std::vector results; + Cpp::CodeComplete(results, code.c_str(), 1, cursor_pos + 1); + + m_shared_buffer->set_completions(results); + m_shared_buffer->response_status = SharedMemoryBuffer::ResponseStatus::SUCCESS; + } catch (const std::exception& e) { + m_shared_buffer->response_status = SharedMemoryBuffer::ResponseStatus::SYSTEM_ERROR; + m_shared_buffer->set_error(std::string("Code completion exception: ") + e.what()); + } + } + + void processEvaluation() { + if (!m_interpreter) { + m_shared_buffer->response_status = SharedMemoryBuffer::ResponseStatus::SYSTEM_ERROR; + m_shared_buffer->set_error("Interpreter not initialized"); + return; + } + + try { + std::string code = m_shared_buffer->get_code(); + + int64_t result = Cpp::Evaluate(code.c_str()); + m_shared_buffer->evaluation_result = result; + m_shared_buffer->response_status = SharedMemoryBuffer::ResponseStatus::SUCCESS; + } + catch (const std::exception& e) { + m_shared_buffer->response_status = SharedMemoryBuffer::ResponseStatus::RUNTIME_ERROR; + m_shared_buffer->set_error(std::string("Evaluation exception: ") + e.what()); + } + } + + void cleanup() { + std::lock_guard lock(init_mutex); + initialized.store(false); + + if (m_interpreter) { + // Note: CppInterOp might not have explicit cleanup, + // but setting to nullptr is safer + m_interpreter = nullptr; + } + + if (m_shared_buffer && m_shared_buffer != MAP_FAILED) { + munmap(m_shared_buffer, m_shm_size); + m_shared_buffer = nullptr; + } + + if (m_shm_fd != -1) { + close(m_shm_fd); + shm_unlink(m_shm_name.c_str()); + m_shm_fd = -1; + } + } +}; + +// Signal handler for graceful shutdown +std::atomic CppInterOpProcess::initialized{false}; +std::mutex CppInterOpProcess::init_mutex; +static CppInterOpProcess* g_process = nullptr; + +void signal_handler(int sig) { + if (g_process) { + std::clog << "Received signal " << sig << ", shutting down..." << std::endl; + // The process will exit on next iteration + } +} + +int main(int argc, char* argv[]) { + if (argc < 2 || argc > 3) { + std::cerr << "Usage: " << argv[0] << " [shared_memory_size]" << std::endl; + return 1; + } + + std::string shm_name = argv[1]; + size_t shm_size = sizeof(SharedMemoryBuffer); + + if (argc == 3) { + try { + shm_size = std::stoull(argv[2]); + } catch (const std::exception& e) { + std::cerr << "Invalid shared memory size: " << argv[2] << std::endl; + return 1; + } + } + + // Setup signal handlers + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + + CppInterOpProcess process(shm_name, shm_size); + g_process = &process; + + std::clog << "Initializing CppInterOp process with shared memory '" + << shm_name << "' (size: " << process.getSharedMemorySize() << " bytes)" << std::endl; + + if (!process.initialize()) { + std::cerr << "Failed to initialize CppInterOp process" << std::endl; + return 1; + } + + process.run(); + + g_process = nullptr; + return 0; +} \ No newline at end of file diff --git a/src/xdebugger.cpp b/src/xdebugger.cpp new file mode 100644 index 00000000..ac7c156d --- /dev/null +++ b/src/xdebugger.cpp @@ -0,0 +1,471 @@ +#include "xeus-cpp/xdebugger.hpp" + +#include // For inet_pton(), htons() +#include // For std::chrono (used in sleep_for) +#include +#include +#include +#include +#include // For sockaddr_in, AF_INET +#include // For socket(), connect(), send(), recv() +#include +#include +#include +#include + +#include "nlohmann/json.hpp" +#include "xdebuglldb_client.hpp" +#include "xeus-zmq/xmiddleware.hpp" +#include "xeus/xsystem.hpp" +#include "xinternal_utils.hpp" + +using namespace std::placeholders; + +namespace xcpp +{ + debugger::debugger( + xeus::xcontext& context, + const xeus::xconfiguration& config, + const std::string& user_name, + const std::string& session_id, + const nl::json& debugger_config + ) + : xdebugger_base(context) + , p_debuglldb_client(new xdebuglldb_client( + context, + config, + xeus::get_socket_linger(), + xdap_tcp_configuration(xeus::dap_tcp_type::client, xeus::dap_init_type::parallel, user_name, session_id), + get_event_callback() + )) + , m_lldb_host("127.0.0.1") + , m_lldb_port("") + , m_debugger_config(debugger_config) + , m_is_running(false) + { + // Register request handlers + register_request_handler( + "inspectVariables", + std::bind(&debugger::inspect_variables_request, this, _1), + false + ); + register_request_handler("stackTrace", std::bind(&debugger::stack_trace_request, this, _1), false); + register_request_handler("attach", std::bind(&debugger::attach_request, this, _1), true); + register_request_handler( + "configurationDone", + std::bind(&debugger::configuration_done_request, this, _1), + true + ); + + std::cout << "Debugger initialized with config: " << m_debugger_config.dump() << std::endl; + } + + debugger::~debugger() + { + std::cout << "Stopping debugger..........." << std::endl; + delete p_debuglldb_client; + p_debuglldb_client = nullptr; + } + + bool debugger::start_lldb() + { + std::cout << "debugger::start_lldb" << std::endl; + + // Find a free port for LLDB-DAP + m_lldb_port = xeus::find_free_port(100, 9999, 10099); + if (m_lldb_port.empty()) + { + std::cout << "Failed to find a free port for LLDB-DAP" << std::endl; + return false; + } + + // Log debugger configuration if XEUS_LOG is set + if (std::getenv("XEUS_LOG") != nullptr) + { + std::ofstream out("xeus.log", std::ios_base::app); + out << "===== DEBUGGER CONFIG =====" << std::endl; + out << m_debugger_config.dump() << std::endl; + } + + // Build C++ code to start LLDB-DAP process + std::string code = "#include \n"; + code += "#include \n"; + code += "#include \n"; + code += "#include \n"; + code += "#include \n"; + code += "#include \n"; + code += "#include \n"; + code += "using namespace std;\n\n"; + code += "int main() {\n"; + + // Construct LLDB-DAP command arguments + code += " vector lldb_args = {\"lldb-dap\", \"--port\", \"" + m_lldb_port + "\"};\n"; + // Add additional configuration from m_debugger_config + auto it = m_debugger_config.find("lldb"); + if (it != m_debugger_config.end() && it->is_object()) + { + if (it->contains("initCommands")) + { + std::cout << "Adding init commands to lldb-dap command" << std::endl; + for (const auto& cmd : it->at("initCommands").get>()) + { + std::cout << "Adding command: " << cmd << std::endl; + // Escape quotes in the command for C++ string + std::string escaped_cmd = cmd; + size_t pos = 0; + while ((pos = escaped_cmd.find("\"", pos)) != std::string::npos) + { + escaped_cmd.replace(pos, 1, "\\\""); + pos += 2; + } + while ((pos = escaped_cmd.find("\\", pos)) != std::string::npos + && pos < escaped_cmd.length() - 1) + { + if (escaped_cmd[pos + 1] != '\"') + { + escaped_cmd.replace(pos, 1, "\\\\"); + pos += 2; + } + else + { + pos += 2; + } + } + code += " lldb_args.push_back(\"--init-command\");\n"; + code += " lldb_args.push_back(\"" + escaped_cmd + "\");\n"; + } + } + } + + // Set up log directory and file + std::string log_dir = xeus::get_temp_directory_path() + "/xcpp_debug_logs_" + + std::to_string(xeus::get_current_pid()); + xeus::create_directory(log_dir); + std::string log_file = log_dir + "/lldb-dap.log"; + + // Add code to start the subprocess with proper redirection + code += " string log_file = \"" + log_file + "\";\n"; + code += " \n"; + code += " pid_t pid = fork();\n"; + code += " if (pid == 0) {\n"; + code += " // Child process - redirect stdout/stderr to log file\n"; + code += " int fd = open(log_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);\n"; + code += " if (fd != -1) {\n"; + code += " dup2(fd, STDOUT_FILENO);\n"; + code += " dup2(fd, STDERR_FILENO);\n"; + code += " close(fd);\n"; + code += " }\n"; + code += " \n"; + code += " // Redirect stdin to /dev/null\n"; + code += " int null_fd = open(\"/dev/null\", O_RDONLY);\n"; + code += " if (null_fd != -1) {\n"; + code += " dup2(null_fd, STDIN_FILENO);\n"; + code += " close(null_fd);\n"; + code += " }\n"; + code += " \n"; + code += " // Convert vector to char* array for execvp\n"; + code += " vector args;\n"; + code += " for (auto& arg : lldb_args) {\n"; + code += " args.push_back(const_cast(arg.c_str()));\n"; + code += " }\n"; + code += " args.push_back(nullptr);\n"; + code += " \n"; + code += " execvp(\"lldb-dap\", args.data());\n"; + code += " \n"; + code += " // If execvp fails\n"; + code += " cerr << \"Failed to execute lldb-dap\" << endl;\n"; + code += " exit(1);\n"; + code += " }\n"; + code += " else if (pid > 0) {\n"; + code += " // Parent process\n"; + code += " cout << \"LLDB-DAP process started, PID: \" << pid << endl;\n"; + code += " \n"; + code += " // Check if process is still running\n"; + code += " int status;\n"; + code += " if (waitpid(pid, &status, WNOHANG) != 0) {\n"; + code += " cerr << \"LLDB-DAP process exited early\" << endl;\n"; + code += " return 1;\n"; + code += " }\n"; + code += " \n"; + code += " cout << \"LLDB-DAP started successfully\" << endl;\n"; + code += " }\n"; + code += " else {\n"; + code += " cerr << \"fork() failed\" << endl;\n"; + code += " return 1;\n"; + code += " }\n"; + code += " \n"; + code += " return 0;\n"; + code += "}\n"; + + std::cout << "Starting LLDB-DAP with port: " << m_lldb_port << std::endl; + + // Execute the C++ code via control messenger + nl::json json_code; + json_code["code"] = code; + nl::json rep = xdebugger::get_control_messenger().send_to_shell(json_code); + std::string status = rep["status"].get(); + + std::cout << "LLDB-DAP start response: " << rep.dump() << std::endl; + + if (status != "ok") + { + std::string ename = rep["ename"].get(); + std::string evalue = rep["evalue"].get(); + std::vector traceback = rep["traceback"].get>(); + std::clog << "Exception raised when trying to start LLDB-DAP" << std::endl; + for (std::size_t i = 0; i < traceback.size(); ++i) + { + std::clog << traceback[i] << std::endl; + } + std::clog << ename << " - " << evalue << std::endl; + return false; + } + else + { + std::cout << xcpp::green_text("LLDB-DAP process started successfully") << std::endl; + } + + m_is_running = true; + return status == "ok"; + } + + bool debugger::start() + { + std::cout << "Starting debugger..." << std::endl; + + // Start LLDB-DAP process + static bool lldb_started = start_lldb(); + if (!lldb_started) + { + std::cout << "Failed to start LLDB-DAP" << std::endl; + return false; + } + // Bind xeus debugger sockets for Jupyter communication + std::string controller_end_point = xeus::get_controller_end_point("debugger"); + std::string controller_header_end_point = xeus::get_controller_end_point("debugger_header"); + std::string publisher_end_point = xeus::get_publisher_end_point(); + bind_sockets(controller_header_end_point, controller_end_point); + + std::cout << "Debugger sockets bound to: " << controller_end_point << std::endl; + std::cout << "Debugger header sockets bound to: " << controller_header_end_point << std::endl; + std::cout << "Publisher sockets bound to: " << publisher_end_point << std::endl; + std::cout << "LLDB-DAP host: " << m_lldb_host << ", port: " << m_lldb_port << std::endl; + + // Start LLDB-DAP client thread (for ZMQ communication) + std::string lldb_endpoint = "tcp://" + m_lldb_host + ":" + m_lldb_port; + std::thread client( + &xdebuglldb_client::start_debugger, + p_debuglldb_client, + lldb_endpoint, + publisher_end_point, + controller_end_point, + controller_header_end_point + ); + client.detach(); + + // Now test direct TCP communication + nl::json init_request = { + {"seq", 1}, + {"type", "request"}, + {"command", "initialize"}, + {"arguments", + {{"adapterID", "xcpp17"}, + {"clientID", "jupyterlab"}, + {"clientName", "JupyterLab"}, + {"columnsStartAt1", true}, + {"linesStartAt1", true}, + {"locale", "en"}, + {"pathFormat", "path"}, + {"supportsRunInTerminalRequest", true}, + {"supportsVariablePaging", true}, + {"supportsVariableType", true}}} + }; + // Also test ZMQ path + send_recv_request("REQ"); + + // std::cout << forward_message(init_request).dump() << std::endl; + + // Create temporary folder for cell code + std::string tmp_folder = get_tmp_prefix(); + xeus::create_directory(tmp_folder); + + return true; + } + + // Dummy implementations for other methods + nl::json debugger::inspect_variables_request(const nl::json& message) + { + // Placeholder DAP response + // std::cout << "Sending setBreakpoints request..." << std::endl; + // nl::json breakpoint_request = { + // {"seq", 3}, + // {"type", "request"}, + // {"command", "setBreakpoints"}, + // {"arguments", { + // {"source", { + // {"name", "input_line_1"}, + // {"path", "/Users/abhinavkumar/Desktop/Coding/Testing/input_line_1"} + // }}, + // {"breakpoints", {{{"line", 8}}}}, + // {"lines", {8}}, + // {"sourceModified", false} + // }} + // }; + // nl::json breakpoint_reply = forward_message(breakpoint_request); + // std::cout << "Breakpoint reply: " << breakpoint_reply.dump() << std::endl; + // nl::json config_done_request = { + // {"seq", 4}, + // {"type", "request"}, + // {"command", "configurationDone"} + // }; + // nl::json config_reply = forward_message(config_done_request); + // std::cout << "Configuration done reply: " << config_reply.dump() << std::endl; + + // nl::json run_request = { + // {"seq", 5}, + // {"type", "request"}, + // {"command", "continue"}, + // {"arguments", nl::json::object()} + // }; + // nl::json run_reply = forward_message(run_request); + // std::cout << "Continue reply: " << run_reply.dump() << std::endl; + + std::cout << "inspect_variables_request not implemented" << std::endl; + std::cout << message.dump() << std::endl; + nl::json reply = { + {"type", "response"}, + {"request_seq", message["seq"]}, + {"success", true}, + {"command", message["command"]}, + {"body", + {{"variables", + {{{"name", "a"}, + {"value", "100"}, + {"type", "int"}, + {"evaluateName", "a"}, + {"variablesReference", 0}}, + {{"name", "b"}, + {"value", "1000"}, + {"type", "int"}, + {"evaluateName", "b"}, + {"variablesReference", 0}}}}}} + }; + return reply; + } + + nl::json debugger::stack_trace_request(const nl::json& message) + { + // Placeholder DAP response + std::cout << "stack_trace_request not implemented" << std::endl; + nl::json reply = { + {"type", "response"}, + {"request_seq", message["seq"]}, + {"success", false}, + {"command", message["command"]}, + {"message", "stackTrace not implemented"}, + {"body", {{"stackFrames", {}}}} + }; + return reply; + } + + nl::json debugger::attach_request(const nl::json& message) + { + // Placeholder DAP response + std::cout << "debugger::attach_request" << std::endl; + nl::json attach_request = { + {"seq", 2}, + {"type", "request"}, + {"command", "attach"}, + {"arguments", { + {"pid", message["arguments"].value("pid", 0)}, + {"program", message["arguments"].value("program", "")}, + {"stopOnEntry", message["arguments"].value("stopOnEntry", false)}, + {"initCommands", message["arguments"].value("initCommands", nl::json::array())} + }} + }; + nl::json reply = forward_message(attach_request); + std::cout << "Attach request sent: " << reply.dump() << std::endl; + return reply; + } + + nl::json debugger::configuration_done_request(const nl::json& message) + { + // Minimal DAP response to allow DAP workflow to proceed + std::cout << "configuration_done_request not implemented" << std::endl; + nl::json reply = { + {"type", "response"}, + {"request_seq", message["seq"]}, + {"success", true}, + {"command", message["command"]} + }; + return reply; + } + + nl::json debugger::variables_request_impl(const nl::json& message) + { + // Placeholder DAP response + std::cout << "variables_request_impl not implemented" << std::endl; + nl::json reply = { + {"type", "response"}, + {"request_seq", message["seq"]}, + {"success", false}, + {"command", message["command"]}, + {"message", "variables not implemented"}, + {"body", {{"variables", {}}}} + }; + return reply; + } + + void debugger::stop() + { + // Placeholder: Log stop attempt + std::cout << "Debugger stop called" << std::endl; + std::string controller_end_point = xeus::get_controller_end_point("debugger"); + std::string controller_header_end_point = xeus::get_controller_end_point("debugger_header"); + unbind_sockets(controller_header_end_point, controller_end_point); + } + + xeus::xdebugger_info debugger::get_debugger_info() const + { + // Placeholder debugger info + std::cout << "get_debugger_info called" << std::endl; + return xeus::xdebugger_info( + xeus::get_tmp_hash_seed(), + get_tmp_prefix(), + get_tmp_suffix(), + true, // Supports exceptions + {"C++ Exceptions"}, + true // Supports breakpoints + ); + } + + std::string debugger::get_cell_temporary_file(const std::string& code) const + { + // Placeholder: Return a dummy temporary file path + std::cout << "get_cell_temporary_file called" << std::endl; + std::string tmp_file = get_tmp_prefix() + "/cell_tmp.cpp"; + std::ofstream out(tmp_file); + out << code; + out.close(); + return tmp_file; + } + + std::unique_ptr make_cpp_debugger( + xeus::xcontext& context, + const xeus::xconfiguration& config, + const std::string& user_name, + const std::string& session_id, + const nl::json& debugger_config + ) + { + std::cout << "Creating C++ debugger" << std::endl; + std::cout << "Debugger config: " << debugger_config.dump() << std::endl; + std::cout << "User name: " << user_name << std::endl; + std::cout << "Session ID: " << session_id << std::endl; + // std::cout << "Context: " << context.get_context_id() << std::endl; + // std::cout << "Config: " << config.dump() << std::endl; + return std::unique_ptr( + new debugger(context, config, user_name, session_id, debugger_config) + ); + } +} \ No newline at end of file diff --git a/src/xdebuglldb_client.cpp b/src/xdebuglldb_client.cpp new file mode 100644 index 00000000..e914ac5b --- /dev/null +++ b/src/xdebuglldb_client.cpp @@ -0,0 +1,61 @@ +#include "xdebuglldb_client.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "nlohmann/json.hpp" +#include "xeus/xmessage.hpp" +#include + +namespace nl = nlohmann; + +namespace xcpp +{ + xdebuglldb_client::xdebuglldb_client( + xeus::xcontext& context, + const xeus::xconfiguration& config, + int socket_linger, + const xdap_tcp_configuration& dap_config, + const event_callback& cb + ) + : base_type(context, config, socket_linger, dap_config, cb) + { + std::cout << "xdebuglldb_client initialized" << std::endl; + } + + + void xdebuglldb_client::handle_event(nl::json message) + { + // Forward DAP events to the base class (e.g., "stopped" events from LLDB-DAP) + forward_event(std::move(message)); + } + + nl::json xdebuglldb_client::get_stack_frames(int thread_id, int seq) + { + // Construct a DAP stackTrace request + nl::json request = { + {"type", "request"}, + {"seq", seq}, + {"command", "stackTrace"}, + {"arguments", {{"threadId", thread_id}}} + }; + + // Send the request + send_dap_request(std::move(request)); + + // Wait for the response + nl::json reply = wait_for_message( + [](const nl::json& message) + { + return message["type"] == "response" && message["command"] == "stackTrace"; + } + ); + + return reply["body"]["stackFrames"]; + } +} \ No newline at end of file diff --git a/src/xdebuglldb_client.hpp b/src/xdebuglldb_client.hpp new file mode 100644 index 00000000..3ea029d6 --- /dev/null +++ b/src/xdebuglldb_client.hpp @@ -0,0 +1,33 @@ +#ifndef XEUS_CPP_DEBUGLLDB_CLIENT_HPP +#define XEUS_CPP_DEBUGLLDB_CLIENT_HPP + +#include "xeus-zmq/xdap_tcp_client.hpp" + +namespace xcpp +{ + using xeus::xdap_tcp_client; + using xeus::xdap_tcp_configuration; + + class xdebuglldb_client : public xdap_tcp_client + { + public: + + using base_type = xdap_tcp_client; + using event_callback = base_type::event_callback; + + xdebuglldb_client(xeus::xcontext& context, + const xeus::xconfiguration& config, + int socket_linger, + const xdap_tcp_configuration& dap_config, + const event_callback& cb); + + virtual ~xdebuglldb_client() = default; + + private: + + void handle_event(nl::json message) override; + nl::json get_stack_frames(int thread_id, int seq); + }; +} + +#endif \ No newline at end of file diff --git a/src/xinternal_utils.cpp b/src/xinternal_utils.cpp new file mode 100644 index 00000000..20974898 --- /dev/null +++ b/src/xinternal_utils.cpp @@ -0,0 +1,79 @@ +#include "xinternal_utils.hpp" + +#ifdef _WIN32 +#include +#else +#include +#endif + +namespace xcpp +{ + std::string red_text(const std::string& text) + { +#ifdef _WIN32 + HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO consoleInfo; + GetConsoleScreenBufferInfo(hConsole, &consoleInfo); + SetConsoleTextAttribute(hConsole, FOREGROUND_RED); + std::string result = text; + SetConsoleTextAttribute(hConsole, consoleInfo.wAttributes); + return result; +#else + return "\033[0;31m" + text + "\033[0m"; +#endif + } + + std::string green_text(const std::string& text) + { +#ifdef _WIN32 + HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO consoleInfo; + GetConsoleScreenBufferInfo(hConsole, &consoleInfo); + SetConsoleTextAttribute(hConsole, FOREGROUND_GREEN); + std::string result = text; + SetConsoleTextAttribute(hConsole, consoleInfo.wAttributes); + return result; +#else + return "\033[0;32m" + text + "\033[0m"; +#endif + } + + std::string blue_text(const std::string& text) + { +#ifdef _WIN32 + HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO consoleInfo; + GetConsoleScreenBufferInfo(hConsole, &consoleInfo); + SetConsoleTextAttribute(hConsole, FOREGROUND_BLUE); + std::string result = text; + SetConsoleTextAttribute(hConsole, consoleInfo.wAttributes); + return result; +#else + return "\033[0;34m" + text + "\033[0m"; +#endif + } + + std::string highlight(const std::string& code) + { + // Placeholder: No syntax highlighting implemented + // In a real implementation, use a C++ library (e.g., libclang or a custom highlighter) + return code; + } + + std::string get_tmp_prefix() + { + return xeus::get_tmp_prefix("xcpp"); + } + + std::string get_tmp_suffix() + { + return ".cpp"; + } + + std::string get_cell_tmp_file(const std::string& content) + { + return xeus::get_cell_tmp_file(get_tmp_prefix(), + content, + get_tmp_suffix()); + } +} \ No newline at end of file diff --git a/src/xinternal_utils.hpp b/src/xinternal_utils.hpp new file mode 100644 index 00000000..bf084172 --- /dev/null +++ b/src/xinternal_utils.hpp @@ -0,0 +1,24 @@ +#ifndef XEUS_CPP_INTERNAL_UTILS_HPP +#define XEUS_CPP_INTERNAL_UTILS_HPP + +#include + +#include "xeus/xsystem.hpp" + +namespace xcpp +{ + // Colorize text for terminal output + std::string red_text(const std::string& text); + std::string green_text(const std::string& text); + std::string blue_text(const std::string& text); + + // Placeholder for C++ syntax highlighting + std::string highlight(const std::string& code); + + // Temporary file utilities for Jupyter cells + std::string get_tmp_prefix(); + std::string get_tmp_suffix(); + std::string get_cell_tmp_file(const std::string& content); +} + +#endif \ No newline at end of file diff --git a/src/xinterpreter.cpp b/src/xinterpreter.cpp index 14972324..a9d9fbe6 100644 --- a/src/xinterpreter.cpp +++ b/src/xinterpreter.cpp @@ -27,46 +27,14 @@ using Args = std::vector; -void* createInterpreter(const Args &ExtraArgs = {}) { - Args ClangArgs = {/*"-xc++"*/"-v"}; - if (std::find_if(ExtraArgs.begin(), ExtraArgs.end(), [](const std::string& s) { - return s == "-resource-dir";}) == ExtraArgs.end()) { - std::string resource_dir = Cpp::DetectResourceDir(); - if (!resource_dir.empty()) { - ClangArgs.push_back("-resource-dir"); - ClangArgs.push_back(resource_dir.c_str()); - } else { - std::cerr << "Failed to detect the resource-dir\n"; - } - } - std::vector CxxSystemIncludes; - Cpp::DetectSystemCompilerIncludePaths(CxxSystemIncludes); - for (const std::string& CxxInclude : CxxSystemIncludes) { - ClangArgs.push_back("-isystem"); - ClangArgs.push_back(CxxInclude.c_str()); - } - ClangArgs.insert(ClangArgs.end(), ExtraArgs.begin(), ExtraArgs.end()); - // FIXME: We should process the kernel input options and conditionally pass - // the gpu args here. - return Cpp::CreateInterpreter(ClangArgs/*, {"-cuda"}*/); -} + +#include "xeus-cpp/xshared_memory.hpp" +#include "xcppinterop_client.hpp" using namespace std::placeholders; namespace xcpp { - struct StreamRedirectRAII { - std::string &err; - StreamRedirectRAII(std::string &e) : err(e) { - Cpp::BeginStdStreamCapture(Cpp::kStdErr); - Cpp::BeginStdStreamCapture(Cpp::kStdOut); - } - ~StreamRedirectRAII() { - std::string out = Cpp::EndStdStreamCapture(); - err = Cpp::EndStdStreamCapture(); - std::cout << out; - } - }; void interpreter::configure_impl() { @@ -96,8 +64,8 @@ int __get_cxx_version () { } __get_cxx_version () )"; - auto cxx_version = Cpp::Evaluate(code); - return std::to_string(cxx_version); + + return "17"; } interpreter::interpreter(int argc, const char* const* argv) : @@ -108,8 +76,6 @@ __get_cxx_version () , m_cerr_buffer(std::bind(&interpreter::publish_stderr, this, _1)) { //NOLINTNEXTLINE (cppcoreguidelines-pro-bounds-pointer-arithmetic) - createInterpreter(Args(argv ? argv + 1 : argv, argv + argc)); - m_version = get_stdopt(); redirect_output(); init_preamble(); init_magic(); @@ -120,6 +86,12 @@ __get_cxx_version () restore_output(); } + void interpreter::set_cppinterop_client(std::shared_ptr client) + { + m_cppinterop_client = std::move(client); + m_version = get_stdopt(); + } + void interpreter::execute_request_impl( send_reply_callback cb, int /*execution_count*/, @@ -162,13 +134,17 @@ __get_cxx_version () std::cerr.rdbuf(&null); } - std::string err; + std::string output, err; // Attempt normal evaluation try { - StreamRedirectRAII R(err); - compilation_result = Cpp::Process(code.c_str()); + compilation_result = m_cppinterop_client->processCode(code, output, err); + + // Print output to stdout + if (!output.empty()) { + std::cout << output; + } } catch (std::exception& e) { @@ -312,6 +288,7 @@ __get_cxx_version () "\n" " xeus-cpp: a C++ Jupyter kernel - based on Clang-repl\n"; result["banner"] = banner; + result["debugger"] = true; result["language_info"]["name"] = "C++"; result["language_info"]["version"] = m_version; result["language_info"]["mimetype"] = "text/x-c++src"; @@ -330,6 +307,111 @@ __get_cxx_version () restore_output(); } + nl::json interpreter::internal_request_impl(const nl::json& content) +{ + std::string code = content.value("code", ""); + nl::json reply; + std::cout << "Executing internal request with code:\n" << code << std::endl; + try + { + // Create temporary files for compilation + std::string temp_dir = xeus::get_temp_directory_path(); + std::string temp_source = temp_dir + "/xcpp_temp_" + std::to_string(xeus::get_current_pid()) + ".cpp"; + std::string temp_executable = temp_dir + "/xcpp_temp_" + std::to_string(xeus::get_current_pid()); + std::string temp_output = temp_dir + "/xcpp_output_" + std::to_string(xeus::get_current_pid()) + ".txt"; + std::string temp_error = temp_dir + "/xcpp_error_" + std::to_string(xeus::get_current_pid()) + ".txt"; + + // Write C++ code to temporary file + std::ofstream source_file(temp_source); + if (!source_file.is_open()) + { + throw std::runtime_error("Failed to create temporary source file"); + } + + source_file << code; + source_file.close(); + + // Compile the C++ code using clang++ + std::string compile_cmd = "clang++ " + temp_source + + " -o " + temp_executable + " 2>" + temp_error; + + int compile_result = std::system(compile_cmd.c_str()); + + if (compile_result != 0) + { + // Compilation failed - read error messages + std::ifstream error_file(temp_error); + std::string error_msg; + std::string line; + while (std::getline(error_file, line)) + { + error_msg += line + "\n"; + } + error_file.close(); + + // Clean up temporary files + std::remove(temp_source.c_str()); + std::remove(temp_error.c_str()); + + reply["status"] = "error"; + reply["ename"] = "CompilationError"; + reply["evalue"] = "C++ compilation failed"; + reply["traceback"] = std::vector{error_msg}; + return reply; + } + + // Execute the compiled program + std::string execute_cmd = temp_executable + " >" + temp_output + " 2>" + temp_error; + int execute_result = std::system(execute_cmd.c_str()); + + // Read output + std::ifstream output_file(temp_output); + std::string output; + std::string line; + while (std::getline(output_file, line)) + { + output += line + "\n"; + } + output_file.close(); + + // Read errors + std::ifstream error_file(temp_error); + std::string error_output; + while (std::getline(error_file, line)) + { + error_output += line + "\n"; + } + error_file.close(); + + // Clean up temporary files + std::remove(temp_source.c_str()); + std::remove(temp_executable.c_str()); + std::remove(temp_output.c_str()); + std::remove(temp_error.c_str()); + + if (execute_result != 0) + { + reply["status"] = "error"; + reply["ename"] = "RuntimeError"; + reply["evalue"] = "C++ program execution failed"; + reply["traceback"] = std::vector{error_output}; + } + else + { + reply["status"] = "ok"; + } + } + catch (const std::exception& e) + { + reply["status"] = "error"; + reply["ename"] = "SystemError"; + reply["evalue"] = e.what(); + reply["traceback"] = std::vector{e.what()}; + } + + return reply; +} + void interpreter::redirect_output() { p_cout_strbuf = std::cout.rdbuf(); diff --git a/test/Notebooks/xeus-cpp_output.ipynb b/test/Notebooks/xeus-cpp_output.ipynb new file mode 100644 index 00000000..4f61dbdd --- /dev/null +++ b/test/Notebooks/xeus-cpp_output.ipynb @@ -0,0 +1,811 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "be1c8c6c-3fbe-4f53-9deb-8496c43d26ad", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [] + }, + "source": [ + "## Output and error streams\n", + "\n", + "`std::cout` and `std::cerr` are redirected to the notebook frontend." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "56c6f89e-205e-4099-bfbe-4d84e45f4390", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [], + "vscode": { + "languageId": "c++" + } + }, + "outputs": [], + "source": [ + "#include " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9bd7f767-6c22-4a1b-a1e2-cd4184fd0367", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [], + "vscode": { + "languageId": "c++" + } + }, + "outputs": [], + "source": [ + "std::cout << \"some output\";" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9622f20f-5925-4544-a97b-aada3a14209a", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [], + "vscode": { + "languageId": "c++" + } + }, + "outputs": [], + "source": [ + "std::cerr << \"some error\";" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7af0c962-17dc-47d4-9772-b8a06e2bda3a", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [], + "vscode": { + "languageId": "c++" + } + }, + "outputs": [], + "source": [ + "int j = 5;\n", + "std::cout << j << std::endl;" + ] + }, + { + "cell_type": "markdown", + "id": "526a7dba-4001-47d5-a116-95423118e100", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [] + }, + "source": [ + "# Interpreting the C++ programming language\n", + "\n", + "You can define functions, classes, templates, etc ..." + ] + }, + { + "cell_type": "markdown", + "id": "e5b116ce-ced1-4aa4-b14e-ef7d2606202e", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [] + }, + "source": [ + "## Functions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "86b08f22-e16c-4eac-917d-ae6eeb7ec7cb", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [], + "vscode": { + "languageId": "c++" + } + }, + "outputs": [], + "source": [ + "double sqr(double a)\n", + "{\n", + " return a * a;\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5aff6711-54bf-4280-a496-c9f7c683eee5", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [], + "vscode": { + "languageId": "c++" + } + }, + "outputs": [], + "source": [ + "double a = 2.5;\n", + "double asqr = sqr(a);\n", + "std::cout << asqr << std::endl;" + ] + }, + { + "cell_type": "markdown", + "id": "5b3959b0-9dc7-41a4-bba1-e20abd0765f7", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [] + }, + "source": [ + "## Classes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d981a53b-8185-49c5-8a30-02453cc1b9e9", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [], + "vscode": { + "languageId": "c++" + } + }, + "outputs": [], + "source": [ + "class Foo\n", + "{\n", + "public:\n", + "\n", + " virtual ~Foo() {}\n", + " \n", + " virtual void print(double value) const\n", + " {\n", + " std::cout << \"Foo value = \" << value << std::endl;\n", + " }\n", + "};" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "195d513f-d5cb-4e3d-a6cb-ae99dfcd9aab", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [], + "vscode": { + "languageId": "c++" + } + }, + "outputs": [], + "source": [ + "Foo bar;\n", + "bar.print(1.2);" + ] + }, + { + "cell_type": "markdown", + "id": "9ecc1588-cb6e-4676-bb16-b9938e980b06", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [] + }, + "source": [ + "## Polymorphism" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4df90bea-5c9e-462e-bd20-d80fd169b7b6", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [], + "vscode": { + "languageId": "c++" + } + }, + "outputs": [], + "source": [ + "class Bar : public Foo\n", + "{\n", + "public:\n", + "\n", + " virtual ~Bar() {}\n", + " \n", + " virtual void print(double value) const\n", + " {\n", + " std::cout << \"Bar value = \" << 2 * value << std::endl;\n", + " }\n", + "};" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f7dbbcc2-0f00-48eb-8bb9-94e871afa2a7", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [], + "vscode": { + "languageId": "c++" + } + }, + "outputs": [], + "source": [ + "Foo* bar2 = new Bar;\n", + "bar2->print(1.2);\n", + "delete bar2;" + ] + }, + { + "cell_type": "markdown", + "id": "094f4ca7-0aa5-4121-bfff-bf5db1d42c5d", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [] + }, + "source": [ + "## Templates" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0df4f3a5-25a1-4548-ba63-54887c770dad", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [], + "vscode": { + "languageId": "c++" + } + }, + "outputs": [], + "source": [ + "#include \n", + "\n", + "template \n", + "class FooT\n", + "{\n", + "public:\n", + " \n", + " explicit FooT(const T& t) : m_t(t) {}\n", + " \n", + " void print() const\n", + " {\n", + " std::cout << typeid(T).name() << \" m_t = \" << m_t << std::endl;\n", + " }\n", + " \n", + "private:\n", + " \n", + " T m_t;\n", + "};" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e7bcab70-b9db-409c-aa04-9c64b413e266", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [], + "vscode": { + "languageId": "c++" + } + }, + "outputs": [], + "source": [ + "FooT foot(1.2);\n", + "foot.print();" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "de6ba06d-ed19-40b6-b31d-b8782fb81174", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [], + "vscode": { + "languageId": "c++" + } + }, + "outputs": [], + "source": [ + "#include \"xcpp/xdisplay.hpp\"" + ] + }, + { + "cell_type": "markdown", + "id": "387d6a7a-7ca4-41d6-9809-4040641db338", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [] + }, + "source": [ + "### Audio example" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5aab5534-8420-4341-bf59-546d5f24bbed", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [], + "vscode": { + "languageId": "c++" + } + }, + "outputs": [], + "source": [ + "#include \n", + "#include \n", + "\n", + "#include \"nlohmann/json.hpp\"\n", + "\n", + "#include \"xeus/xbase64.hpp\"\n", + "\n", + "namespace nl = nlohmann;\n", + "\n", + "namespace au\n", + "{\n", + " struct audio\n", + " { \n", + " inline audio(const std::string& filename)\n", + " {\n", + " std::ifstream fin(filename, std::ios::binary); \n", + " m_buffer << fin.rdbuf();\n", + " }\n", + " \n", + " std::stringstream m_buffer;\n", + " };\n", + " \n", + " nl::json mime_bundle_repr(const audio& a)\n", + " {\n", + " auto bundle = nl::json::object();\n", + " bundle[\"text/html\"] =\n", + " std::string(\"\";\n", + " return bundle;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0b9e8220-fe5a-498e-93a1-a93e71bda629", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [], + "vscode": { + "languageId": "c++" + } + }, + "outputs": [], + "source": [ + "au::audio drums(\"audio/audio.wav\");" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "faee9bb7-22e4-49ad-8483-38cc1a044b19", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [], + "vscode": { + "languageId": "c++" + } + }, + "outputs": [], + "source": [ + "xcpp::display(drums);" + ] + }, + { + "cell_type": "markdown", + "id": "d32274d3-61eb-4763-9b10-b3d4db09ae5c", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [] + }, + "source": [ + "### Update-display" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5ba5126e-ea3a-4393-84a1-99fac70ae5e4", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [], + "vscode": { + "languageId": "c++" + } + }, + "outputs": [], + "source": [ + "#include \n", + "#include \"xcpp/xdisplay.hpp\"\n", + "\n", + "#include \"nlohmann/json.hpp\"\n", + "\n", + "namespace nl = nlohmann;\n", + "\n", + "namespace ht\n", + "{\n", + " struct html\n", + " { \n", + " inline html(const std::string& content)\n", + " {\n", + " m_content = content;\n", + " }\n", + " std::string m_content;\n", + " };\n", + "\n", + " nl::json mime_bundle_repr(const html& a)\n", + " {\n", + " auto bundle = nl::json::object();\n", + " bundle[\"text/html\"] = a.m_content;\n", + " return bundle;\n", + " }\n", + "}\n", + "\n", + "// A blue rectangle\n", + "ht::html rect(R\"(\n", + "
\n", + "Original\n", + "
)\");" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a3de3009-bf70-4adf-9471-900be0bc3d2d", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [], + "vscode": { + "languageId": "c++" + } + }, + "outputs": [], + "source": [ + "xcpp::display(rect, \"some_display_id\");" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "629fa3d5-6c5d-40ad-937d-6257ae6c827d", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [], + "vscode": { + "languageId": "c++" + } + }, + "outputs": [], + "source": [ + "// Update the rectangle to be red\n", + "rect.m_content = R\"(\n", + "
\n", + "Updated\n", + "
)\";\n", + "\n", + "xcpp::display(rect, \"some_display_id\", true);" + ] + }, + { + "cell_type": "markdown", + "id": "d4828c73-b061-4f8f-9966-f45d570c6567", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [] + }, + "source": [ + "### Clear output" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8391fa79-f365-4792-b1f6-b2f68d79a3d6", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [], + "vscode": { + "languageId": "c++" + } + }, + "outputs": [], + "source": [ + "#include \n", + "#include \n", + "#include \n", + "\n", + "#include \"xcpp/xdisplay.hpp\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16ab8930-6639-4c8d-855f-58ec70a62c17", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [], + "vscode": { + "languageId": "c++" + } + }, + "outputs": [], + "source": [ + "std::cout << \"hello\" << std::endl;\n", + "std::this_thread::sleep_for(std::chrono::seconds(1));\n", + "xcpp::clear_output(); // will flicker when replacing \"hello\" with \"goodbye\"\n", + "std::this_thread::sleep_for(std::chrono::seconds(1));\n", + "std::cout << \"goodbye\" << std::endl;" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "347331f2-98cf-4111-989c-7e34b3d0a309", + "metadata": { + "papermill": { + "duration": null, + "end_time": null, + "exception": null, + "start_time": null, + "status": "completed" + }, + "tags": [], + "vscode": { + "languageId": "c++" + } + }, + "outputs": [], + "source": [ + "std::cout << \"hello\" << std::endl;\n", + "std::this_thread::sleep_for(std::chrono::seconds(1));\n", + "xcpp::clear_output(true); // prevents flickering\n", + "std::this_thread::sleep_for(std::chrono::seconds(1));\n", + "std::cout << \"goodbye\" << std::endl;" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "C++20", + "language": "cpp", + "name": "xcpp20" + }, + "language_info": { + "codemirror_mode": "text/x-c++src", + "file_extension": ".cpp", + "mimetype": "text/x-c++src", + "name": "C++", + "version": "20" + }, + "papermill": { + "default_parameters": {}, + "duration": 1.02855, + "end_time": "2025-05-08T08:42:49.221498", + "environment_variables": {}, + "exception": null, + "input_path": "Notebooks/xeus-cpp.ipynb", + "output_path": "Notebooks/xeus-cpp_output.ipynb", + "parameters": {}, + "start_time": "2025-05-08T08:42:48.192948", + "version": "2.6.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file