Jelajahi Sumber

Merge pull request #379 from lightovernight/gsoc-2025-debugger

Gsoc 2025 debugger
lightovernight 6 bulan lalu
induk
melakukan
ec8f8f473e

+ 19 - 0
include/pocketpy/debugger/core.h

@@ -0,0 +1,19 @@
+#pragma once
+
+#include "pocketpy/common/sstream.h"
+#include "pocketpy/common/str.h"
+#include "pocketpy/common/vector.h"
+#include "pocketpy/pocketpy.h"
+
+enum C11_STEP_MODE { C11_STEP_IN, C11_STEP_OVER, C11_STEP_OUT, C11_STEP_CONTINUE };
+
+void c11_debugger_init(void);
+void c11_debugger_set_step_mode(enum C11_STEP_MODE mode);
+void c11_debugger_on_trace(py_Frame* frame, enum py_TraceEvent event);
+void c11_debugger_frames(c11_sbuf* buffer);
+void c11_debugger_scopes(int frameid, c11_sbuf* buffer);
+bool c11_debugger_unfold_var(int var_id, c11_sbuf* buffer);
+int c11_debugger_setbreakpoint(const char* filename, int lineno);
+int c11_debugger_reset_breakpoints_by_source(const char* sourcesname);
+int c11_debugger_should_pause(void);
+int c11_debugger_should_keep_pause(void);

+ 1 - 0
include/pocketpy/debugger/dap.h

@@ -0,0 +1 @@
+#pragma once

+ 4 - 1
include/pocketpy/pocketpy.h

@@ -703,12 +703,15 @@ PK_API bool py_pickle_dumps(py_Ref val) PY_RAISE PY_RETURN;
 PK_API bool py_pickle_loads(const unsigned char* data, int size) PY_RAISE PY_RETURN;
 
 /************* Profiler *************/
-
 PK_API void py_profiler_begin();
 PK_API void py_profiler_end();
 PK_API void py_profiler_reset();
 PK_API char* py_profiler_report();
 
+/************* DAP *************/
+PK_API void py_debugger_waitforattach(const char* hostname, unsigned short port);
+PK_API void py_debugger_exit(int exitCode);
+
 /************* Unchecked Functions *************/
 
 PK_API py_ObjectRef py_tuple_data(py_Ref self);

+ 294 - 0
src/debugger/core.c

@@ -0,0 +1,294 @@
+
+
+#include "pocketpy/interpreter/frame.h"
+#include "pocketpy/pocketpy.h"
+#include <ctype.h>
+
+#include "pocketpy/debugger/core.h"
+
+typedef struct c11_debugger_breakpoint {
+    const char* sourcename;
+    int lineno;
+} c11_debugger_breakpoint;
+
+typedef struct c11_debugger_scope_index {
+    int locals_ref;
+    int globals_ref;
+} c11_debugger_scope_index;
+
+#define SMALLMAP_T__HEADER
+#define SMALLMAP_T__SOURCE
+#define K int
+#define V c11_debugger_scope_index
+#define NAME c11_smallmap_d2index
+#include "pocketpy/xmacros/smallmap.h"
+#undef SMALLMAP_T__SOURCE
+#undef SMALLMAP_T__HEADER
+
+static struct c11_debugger {
+    py_Frame* current_frame;
+    const char* current_filename;
+    enum py_TraceEvent current_event;
+
+    int curr_stack_depth;
+    int current_line;
+    int pause_allowed_depth;
+    int step_line;
+    enum C11_STEP_MODE step_mode;
+    bool keep_suspend;
+
+    c11_vector breakpoints;
+    c11_vector py_frames;
+    c11_smallmap_d2index scopes_query_cache;
+
+    #define python_vars py_r7()
+
+} debugger;
+
+inline static void init_structures() {
+    c11_vector__ctor(&debugger.breakpoints, sizeof(c11_debugger_breakpoint));
+    c11_vector__ctor(&debugger.py_frames, sizeof(py_Frame*));
+    c11_smallmap_d2index__ctor(&debugger.scopes_query_cache);
+    py_newlist(python_vars);
+    py_newnil(py_list_emplace(python_vars));
+}
+
+inline static void clear_structures() {
+    c11_vector__clear(&debugger.py_frames);
+    c11_smallmap_d2index__clear(&debugger.scopes_query_cache);
+    py_list_clear(python_vars);
+    py_newnone(py_list_emplace(python_vars));
+}
+
+inline static py_Ref get_variable(int var_ref) {
+    assert(var_ref < py_list_len(python_vars) && var_ref > 0);
+    return py_list_getitem(python_vars, var_ref);
+}
+
+void c11_debugger_init() {
+    debugger.curr_stack_depth = 0;
+    debugger.current_line = -1;
+    debugger.pause_allowed_depth = -1;
+    debugger.step_line = -1;
+    debugger.keep_suspend = false;
+    debugger.step_mode = C11_STEP_CONTINUE;
+    init_structures();
+}
+
+void c11_debugger_on_trace(py_Frame* frame, enum py_TraceEvent event) {
+    debugger.current_frame = frame;
+    debugger.current_event = event;
+    debugger.current_filename = py_Frame_sourceloc(debugger.current_frame, &debugger.current_line);
+    clear_structures();
+    if(event == TRACE_EVENT_LINE) return;
+    event == TRACE_EVENT_PUSH ? debugger.curr_stack_depth++ : debugger.curr_stack_depth--;
+}
+
+void c11_debugger_set_step_mode(enum C11_STEP_MODE mode) {
+    switch(mode) {
+        case C11_STEP_IN: debugger.pause_allowed_depth = INT32_MAX; break;
+        case C11_STEP_OVER:
+            debugger.pause_allowed_depth = debugger.curr_stack_depth;
+            debugger.step_line = debugger.current_line;
+            break;
+        case C11_STEP_OUT: debugger.pause_allowed_depth = debugger.curr_stack_depth - 1; break;
+        case C11_STEP_CONTINUE: debugger.pause_allowed_depth = -1; break;
+    }
+    debugger.step_mode = mode;
+    debugger.keep_suspend = false;
+}
+
+
+int c11_debugger_setbreakpoint(const char* filename, int lineno) {
+    c11_debugger_breakpoint breakpoint = {.sourcename = c11_strdup(filename), .lineno = lineno};
+    c11_vector__push(c11_debugger_breakpoint, &debugger.breakpoints, breakpoint);
+    return debugger.breakpoints.length;
+}
+
+int c11_debugger_reset_breakpoints_by_source(const char* sourcesname) {
+    c11_vector tmp_breakpoints;
+    c11_vector__ctor(&tmp_breakpoints, sizeof(c11_debugger_breakpoint));
+
+    c11__foreach(c11_debugger_breakpoint, &debugger.breakpoints, it) {
+        if(strcmp(it->sourcename, sourcesname) != 0) {
+            c11_debugger_breakpoint* dst =
+                (c11_debugger_breakpoint*)c11_vector__emplace(&tmp_breakpoints);
+            *dst = *it;
+        } else {
+            free((void*)it->sourcename);
+        }
+    }
+
+    c11_vector__swap(&tmp_breakpoints, &debugger.breakpoints);
+    c11_vector__dtor(&tmp_breakpoints);
+    return debugger.breakpoints.length;
+}
+
+int c11_debugger_should_pause() {
+    if(debugger.current_event == TRACE_EVENT_POP) return false;
+    bool should_pause = false;
+    int is_out = debugger.curr_stack_depth <= debugger.pause_allowed_depth;
+    int is_new_line = debugger.current_line != debugger.step_line;
+    switch(debugger.step_mode) {
+        case C11_STEP_IN: should_pause = true; break;
+
+        case C11_STEP_OVER:
+            if(is_new_line && is_out) should_pause = true;
+            break;
+        case C11_STEP_OUT:
+            if(is_out) should_pause = true;
+            break;
+        case C11_STEP_CONTINUE:
+        default: break;
+    }
+    if(debugger.step_mode == C11_STEP_CONTINUE) {
+        c11__foreach(c11_debugger_breakpoint, &debugger.breakpoints, bp) {
+            if(strcmp(debugger.current_filename, bp->sourcename) == 0 &&
+               debugger.current_line == bp->lineno) {
+                should_pause = true;
+                break;
+            }
+        }
+    }
+    if(should_pause) { debugger.keep_suspend = true; }
+    return should_pause;
+}
+
+int c11_debugger_should_keep_pause(void) { return debugger.keep_suspend; }
+
+
+inline static c11_sv sv_from_cstr(const char* str) {
+    c11_sv sv = {.data = str, .size = strlen(str)};
+    return sv;
+}
+
+const inline static char* get_basename(const char* path) {
+    const char* last_slash = strrchr(path, '/');
+#ifdef _WIN32
+    const char* last_backslash = strrchr(path, '\\');
+    if(!last_slash || (last_backslash && last_backslash > last_slash)) {
+        last_slash = last_backslash;
+    }
+#endif
+    return last_slash ? last_slash + 1 : path;
+}
+
+void c11_debugger_frames(c11_sbuf* buffer) {
+    c11_sbuf__write_cstr(buffer, "{\"stackFrames\": [");
+    int idx = 0;
+    py_Frame* now_frame = debugger.current_frame;
+    debugger.py_frames.length = 0;
+    while(now_frame) {
+        if(idx > 0) c11_sbuf__write_char(buffer, ',');
+        int line;
+        const char* filename = py_Frame_sourceloc(now_frame, &line);
+        const char* basename = get_basename(filename);
+        const char* modname = now_frame->co->name->data;
+        pk_sprintf(
+            buffer,
+            "{\"id\": %d, \"name\": %Q, \"line\": %d, \"column\": 1, \"source\": {\"name\": %Q, \"path\": %Q}}",
+            idx,
+            sv_from_cstr(modname),
+            line,
+            sv_from_cstr(basename),
+            sv_from_cstr(filename));
+        c11_vector__push(py_Frame*, &debugger.py_frames, now_frame);
+        now_frame = now_frame->f_back;
+        idx++;
+    }
+    pk_sprintf(buffer, "], \"totalFrames\": %d}", idx);
+}
+
+inline static c11_debugger_scope_index append_new_scope(int frameid) {
+    assert(frameid < debugger.py_frames.length);
+    py_Frame* requested_frame = c11__getitem(py_Frame*, &debugger.py_frames, frameid);
+    int base_index = py_list_len(python_vars);
+    py_Ref new_locals = py_list_emplace(python_vars);
+    py_Ref new_globals = py_list_emplace(python_vars);
+    py_Frame_newlocals(requested_frame, new_locals);
+    py_Frame_newglobals(requested_frame, new_globals);
+    c11_debugger_scope_index result = {.locals_ref = base_index, .globals_ref = base_index + 1};
+    return result;
+}
+
+void c11_debugger_scopes(int frameid, c11_sbuf* buffer) {
+    // query cache
+    c11_debugger_scope_index* result =
+        c11_smallmap_d2index__try_get(&debugger.scopes_query_cache, frameid);
+
+    c11_sbuf__write_cstr(buffer, "{\"scopes\":");
+    const char* scopes_fmt =
+        "[{\"name\": \"locals\", \"variablesReference\": %d, \"expensive\": false}, "
+        "{\"name\": \"globals\", \"variablesReference\": %d, \"expensive\": true}]";
+    if(result != NULL) {
+        pk_sprintf(buffer, scopes_fmt, result->locals_ref, result->globals_ref);
+    } else {
+        c11_debugger_scope_index new_record = append_new_scope(frameid);
+        c11_smallmap_d2index__set(&debugger.scopes_query_cache, frameid, new_record);
+        pk_sprintf(buffer, scopes_fmt, new_record.locals_ref, new_record.globals_ref);
+    }
+    c11_sbuf__write_char(buffer, '}');
+}
+
+bool c11_debugger_unfold_var(int var_id, c11_sbuf* buffer) {
+    py_Ref var = get_variable(var_id);
+    if(!var) return false;
+
+    // 1. extend
+    const char* expand_code = NULL;
+    switch(py_typeof(var)) {
+        case tp_dict:
+        case tp_namedict: expand_code = "[(k,v) for k,v in _0.items()]"; break;
+        case tp_list:
+        case tp_tuple: expand_code = "[(f'[{i}]',v) for i,v in enumerate(_0)]"; break;
+        default: expand_code = "[(k,v) for k,v in _0.__dict__.items()]"; break;
+    }
+    if(!py_smarteval(expand_code, NULL, var)) {
+        py_printexc();
+        return false;
+    }
+    py_Ref kv_list = py_pushtmp();
+    py_assign(kv_list, py_retval());
+    // 2. prepare base_ref
+    int base_index = py_list_len(python_vars);
+    py_Ref base_var_ref = py_pushtmp();
+    py_newint(base_var_ref, base_index);
+
+    // 3. construct DAP JSON
+    const char* dap_code =
+        "{'variables': ["
+        "  {"
+        "    'name': kv[0],"
+        "    'value': repr(kv[1]),"
+        "    'variablesReference': (_1 + i) if (isinstance(kv[1], (dict, list, tuple)) or kv[1].__dict__ is not None) else 0,"
+        "    'type': type(kv[1]).__name__"
+        "  }"
+        "  for i, kv in enumerate(_0)"
+        "]}";
+    if(!py_smarteval(dap_code, NULL, kv_list, base_var_ref)) {
+        py_printexc();
+        return false;
+    }
+    py_Ref dap_obj = py_pushtmp();
+    py_assign(dap_obj, py_retval());
+
+    // 4. extend python_vars
+    if(!py_smartexec("_0.extend([kv[1] for kv in _1])", NULL, python_vars, kv_list)) {
+        py_printexc();
+        return false;
+    }
+    // 5. dump & write
+    if(!py_json_dumps(dap_obj, 0)) {
+        py_printexc();
+        return false;
+    }
+
+    c11_sbuf__write_cstr(buffer, py_tostr(py_retval()));
+
+    // 6. clear
+    py_pop();  // dap_obj
+    py_pop();  // base_var_ref
+    py_pop();  // kv_list
+    return true;
+}
+#undef python_vars

+ 374 - 0
src/debugger/dap.c

@@ -0,0 +1,374 @@
+#include <stdbool.h>
+#include "pocketpy/common/socket.h"
+#include "pocketpy/debugger/core.h"
+#include "pocketpy/objects/base.h"
+
+#define DAP_COMMAND_LIST(X)                                                                        \
+    X(initialize)                                                                                  \
+    X(setBreakpoints)                                                                              \
+    X(attach)                                                                                      \
+    X(next)                                                                                        \
+    X(stepIn)                                                                                      \
+    X(stepOut)                                                                                     \
+    X(continue)                                                                                    \
+    X(stackTrace)                                                                                  \
+    X(scopes)                                                                                      \
+    X(variables)                                                                                   \
+    X(threads)                                                                                     \
+    X(configurationDone)
+
+#define DECLARE_HANDLE_FN(name) void c11_dap_handle_##name(py_Ref arguments, c11_sbuf*);
+DAP_COMMAND_LIST(DECLARE_HANDLE_FN)
+#undef DECLARE_ARG_FN
+
+typedef void (*c11_dap_arg_parser_fn)(py_Ref, c11_sbuf*);
+
+typedef struct {
+    const char* command;
+    c11_dap_arg_parser_fn parser;
+} dap_command_entry;
+
+#define DAP_ENTRY(name) {#name, c11_dap_handle_##name},
+static dap_command_entry dap_command_table[] = {
+    DAP_COMMAND_LIST(DAP_ENTRY){NULL, NULL}
+};
+
+#undef DAP_ENTRY
+
+// #undef DAP_COMMAND_LIST
+
+// static int dap_next_seq = 1;
+static struct c11_dap_server {
+    int dap_next_seq;
+    char buffer_data[1024];
+    char* buffer_begin;
+    int buffer_length;
+    c11_socket_handler server;
+    c11_socket_handler toclient;
+    bool isconfiguredone;
+    bool isatttach;
+} server;
+
+void c11_dap_handle_initialize(py_Ref arguments, c11_sbuf* buffer) {
+    c11_sbuf__write_cstr(buffer, "\"body\":{\"supportsConfigurationDoneRequest\":true}");
+    c11_sbuf__write_char(buffer, ',');
+}
+
+void c11_dap_handle_attach(py_Ref arguments, c11_sbuf* buffer) {
+    server.isatttach = true;
+
+}
+
+void c11_dap_handle_next(py_Ref arguments, c11_sbuf* buffer) {
+    c11_debugger_set_step_mode(C11_STEP_OVER);
+}
+
+void c11_dap_handle_stepIn(py_Ref arguments, c11_sbuf* buffer) {
+    c11_debugger_set_step_mode(C11_STEP_IN);
+}
+
+void c11_dap_handle_stepOut(py_Ref arguments, c11_sbuf* buffer) {
+    c11_debugger_set_step_mode(C11_STEP_OUT);
+}
+
+void c11_dap_handle_continue(py_Ref arguments, c11_sbuf* buffer) {
+    c11_debugger_set_step_mode(C11_STEP_CONTINUE);
+}
+
+void c11_dap_handle_threads(py_Ref arguments, c11_sbuf* buffer) {
+    c11_sbuf__write_cstr(buffer,
+                         "\"body\":{\"threads\":["
+                         "{\"id\":1,\"name\":\"MainThread\"}"
+                         "]}");
+    c11_sbuf__write_char(buffer, ',');
+}
+
+void c11_dap_handle_configurationDone(py_Ref arguments, c11_sbuf* buffer) {
+    server.isconfiguredone = true;
+}
+
+inline static void c11_dap_build_Breakpoint(int id, int line, c11_sbuf* buffer) {
+    pk_sprintf(buffer, "{\"id\":%d,\"verified\":true,\"line\":%d}", id, line);
+}
+
+void c11_dap_handle_setBreakpoints(py_Ref arguments, c11_sbuf* buffer) {
+    if(!py_smarteval("_0['source']['path']", NULL, arguments)) {
+        py_printexc();
+        return;
+    }
+    const char* sourcename = py_tostr(py_retval());
+    if(!py_smarteval("[bp['line'] for bp in _0['breakpoints']]", NULL, arguments)) {
+        py_printexc();
+        return;
+    }
+    int bp_numbers = c11_debugger_reset_breakpoints_by_source(sourcename);
+    c11_sbuf__write_cstr(buffer, "\"body\":");
+    c11_sbuf__write_cstr(buffer, "{\"breakpoints\":[");
+    for(int i = 0; i < py_list_len(py_retval()); i++) {
+        if(i != 0) c11_sbuf__write_char(buffer, ',');
+        int line = py_toint(py_list_getitem(py_retval(), i));
+        c11_debugger_setbreakpoint(sourcename, line);
+        c11_dap_build_Breakpoint(i + bp_numbers, line, buffer);
+    }
+    c11_sbuf__write_cstr(buffer, "]}");
+    c11_sbuf__write_char(buffer, ',');
+}
+
+void c11_dap_handle_stackTrace(py_Ref arguments, c11_sbuf* buffer) {
+    c11_sbuf__write_cstr(buffer, "\"body\":");
+    c11_debugger_frames(buffer);
+    c11_sbuf__write_char(buffer, ',');
+}
+
+void c11_dap_handle_scopes(py_Ref arguments, c11_sbuf* buffer) {
+    int res = py_dict_getitem_by_str(arguments, "frameId");
+    if(res <= 0) {
+        if(res == 0) {
+            printf("[DEBUGGER ERROR] no frameID found\n");
+        } else {
+            py_printexc();
+        }
+        return;
+    }
+    int frameid = py_toint(py_retval());
+    c11_sbuf__write_cstr(buffer, "\"body\":");
+    c11_debugger_scopes(frameid, buffer);
+    c11_sbuf__write_char(buffer, ',');
+}
+
+void c11_dap_handle_variables(py_Ref arguments, c11_sbuf* buffer) {
+    int res = py_dict_getitem_by_str(arguments, "variablesReference");
+    if(res <= 0) {
+        if(res == 0) {
+            printf("[DEBUGGER ERROR] no frameID found\n");
+        } else {
+            py_printexc();
+        }
+        return;
+    }
+    int variablesReference = py_toint(py_retval());
+    c11_sbuf__write_cstr(buffer, "\"body\":");
+    c11_debugger_unfold_var(variablesReference, buffer);
+    c11_sbuf__write_char(buffer, ',');
+}
+
+const char* c11_dap_handle_request(const char* message) {
+    if(!py_json_loads(message)) {
+        py_printexc();
+        return NULL;
+    }
+    py_Ref py_request = py_pushtmp();
+    py_Ref py_arguments = py_pushtmp();
+    py_Ref py_command = py_pushtmp();
+    py_assign(py_request, py_retval());
+
+    int res = py_dict_getitem_by_str(py_request, "command");
+    if(res == -1) {
+        py_printexc();
+        return NULL;
+    } else if(res == 0) {
+        return "cannot find attribute command";
+    }
+    py_assign(py_command, py_retval());
+    const char* command = py_tostr(py_command);
+
+    res = py_dict_getitem_by_str(py_request, "arguments");
+    if(res == -1) {
+        py_printexc();
+        return NULL;
+    }
+    py_assign(py_arguments, py_retval());
+
+    res = py_dict_getitem_by_str(py_request, "seq");
+    if(res == -1) {
+        py_printexc();
+        return NULL;
+    }
+    int request_seq = (res == 1) ? py_toint(py_retval()) : 0;
+
+    c11_sbuf response_buffer;
+    c11_sbuf__ctor(&response_buffer);
+    pk_sprintf(&response_buffer,
+               "{\"seq\":%d,\"type\":\"response\",\"request_seq\":%d,\"command\":\"%s\",",
+               server.dap_next_seq++,
+               request_seq,
+               command);
+    for(dap_command_entry* entry = dap_command_table; entry->command != NULL; entry++) {
+        if(strcmp(entry->command, command) == 0) {
+            entry->parser(py_arguments, &response_buffer);
+            break;
+        }
+    }
+    c11_sbuf__write_cstr(&response_buffer, "\"success\":true}");
+
+    c11_string* c11_string_response = c11_sbuf__submit(&response_buffer);
+    const char* response = c11_strdup(c11_string_response->data);
+    c11_string__delete(c11_string_response);
+
+    py_pop();  // py_arguments
+    py_pop();  // py_request
+    py_pop();  // py_command
+
+    return response;
+}
+
+void c11_dap_send_event(const char* event_name, const char* body_json) {
+    char json[256];
+    int json_len = snprintf(json,
+                            sizeof(json),
+                            "{\"seq\":%d,\"type\":\"event\",\"event\":\"%s\",\"body\":%s}",
+                            server.dap_next_seq++,
+                            event_name,
+                            body_json);
+
+    char header[64];
+    int header_len = snprintf(header, sizeof(header), "Content-Length: %d\r\n\r\n", json_len);
+
+    c11_socket_send(server.toclient, header, header_len);
+    c11_socket_send(server.toclient, json, json_len);
+}
+
+void c11_dap_send_stop_event() {
+    c11_dap_send_event("stopped",
+                       "{\"reason\":\"breakpoint\",\"threadId\":1,\"allThreadsStopped\":true}");
+}
+
+void c11_dap_send_initialized_event() { c11_dap_send_event("initialized", "{}"); }
+
+int c11_dap_read_content_length(const char* buffer, int* header_length) {
+    const char* length_begin = strstr(buffer, "Content-Length: ");
+    if(!length_begin) {
+        printf("[DEBUGGER ERROR] : no Content-Length filed found\n");
+        *header_length = 0;
+        return -1;
+    }
+    length_begin += strlen("Content-Length: ");
+    const char* length_end = strstr(length_begin, "\r\n\r\n");
+    if(!length_end) {
+        printf("[DEBUGGER ERROR] : the seperator should br \\r\\n\\r\\n\n");
+        *header_length = 0;
+        return -1;
+    }
+    char* endptr = NULL;
+    long value = strtol(length_begin, &endptr, 10);
+    if(endptr == length_begin) {
+        printf("[DEBUGGER EORRO] : the number is empty\n");
+        *header_length = 0;
+        return -1;
+    }
+    *header_length = (int)(endptr - buffer) + 4;
+    return (int)value;
+}
+
+const char* c11_dap_read_message() {
+    int message_length =
+        c11_socket_recv(server.toclient, server.buffer_begin, 1024 - server.buffer_length);
+    if(message_length == 0) {
+        printf("[DEBUGGER INFO] : client quit\n");
+        exit(0);
+    }
+    if(message_length < 0) { return NULL; }
+    server.buffer_length += message_length;
+    if(server.buffer_length == 0) return NULL;
+    int header_length;
+    int content_length = c11_dap_read_content_length(server.buffer_begin, &header_length);
+    if(content_length <= 0 || header_length <= 0) {
+        printf("[DEBUGGER ERROR]: invalid DAP header\n");
+        server.buffer_length = 0;
+        server.buffer_begin = server.buffer_data;
+        return NULL;
+    }
+    server.buffer_begin += header_length;
+    server.buffer_length -= header_length;
+    c11_sbuf result;
+    c11_sbuf__ctor(&result);
+    while(content_length > server.buffer_length) {
+        c11_sbuf__write_cstrn(&result, server.buffer_begin, server.buffer_length);
+        content_length -= server.buffer_length;
+        message_length = c11_socket_recv(server.toclient, server.buffer_data, 1024);
+        if(message_length == 0) {
+            printf("[DEBUGGER INFO] : client quit\n");
+            exit(0);
+        }
+        if(message_length < 0) continue;
+        server.buffer_begin = server.buffer_data;
+        server.buffer_length = message_length;
+    }
+    c11_sbuf__write_cstrn(&result, server.buffer_begin, content_length);
+    server.buffer_begin += content_length;
+    server.buffer_length -= content_length;
+    memmove(server.buffer_data, server.buffer_begin, server.buffer_length);
+    server.buffer_begin = server.buffer_data;
+    c11_string* tmp_result = c11_sbuf__submit(&result);
+    const char* dap_message = c11_strdup(tmp_result->data);
+    c11_string__delete(tmp_result);
+    return dap_message;
+}
+
+void c11_dap_init_server(const char* hostname, unsigned short port) {
+    server.dap_next_seq = 1;
+    server.isconfiguredone = false;
+    server.buffer_begin = server.buffer_data;
+    server.server = c11_socket_create(C11_AF_INET, C11_SOCK_STREAM, 0);
+    c11_socket_bind(server.server, hostname, port);
+    c11_socket_listen(server.server, 0);
+    printf("[DEBUGGER INFO] : listen on %s:%hu\n", hostname, port);
+    server.toclient = c11_socket_accept(server.server, NULL, NULL);
+    printf("[DEBUGGER INFO] : connected client\n");
+}
+
+inline static void c11_dap_handle_message() {
+    const char* message = c11_dap_read_message();
+    if(message == NULL) return;
+    printf("[DEBUGGER INFO] read request %s\n", message);
+    const char* response_content = c11_dap_handle_request(message);
+    if(response_content != NULL) { printf("[DEBUGGER INFO] send response %s\n", response_content); }
+    c11_sbuf buffer;
+    c11_sbuf__ctor(&buffer);
+    pk_sprintf(&buffer, "Content-Length: %d\r\n\r\n%s", strlen(response_content), response_content);
+    c11_string* response = c11_sbuf__submit(&buffer);
+    c11_socket_send(server.toclient, response->data, response->size);
+    free((void*)message);
+    free((void*)response_content);
+    c11_string__delete(response);
+}
+
+void c11_dap_configure_debugger() {
+    while(server.isconfiguredone == false) {
+        c11_dap_handle_message();
+        if(server.isatttach) {
+            c11_dap_send_initialized_event();
+            server.isatttach = false;
+        }
+    }
+    printf("[DEBUGGER INFO] : configure done\n");
+}
+
+void c11_dap_tracefunc(py_Frame* frame, enum py_TraceEvent event) {
+    py_sys_settrace(NULL, false);
+    c11_debugger_on_trace(frame, event);
+    c11_dap_handle_message();
+    if(!c11_debugger_should_pause()) {
+        py_sys_settrace(c11_dap_tracefunc, false);
+        return;
+    }
+    c11_dap_send_stop_event();
+    while(c11_debugger_should_keep_pause()) {
+        c11_dap_handle_message();
+    }
+    py_sys_settrace(c11_dap_tracefunc, false);
+}
+
+void py_debugger_waitforattach(const char* hostname, unsigned short port) {
+    c11_dap_init_server(hostname, port);
+    c11_debugger_init();
+    c11_dap_configure_debugger();
+    c11_socket_set_block(server.toclient, 0);
+    py_sys_settrace(c11_dap_tracefunc, true);
+}
+
+void py_debugger_exit(int exitCode) {
+    char body[64];
+    snprintf(body, sizeof(body), "{\"exitCode\":%d}", exitCode);
+    c11_dap_send_event("exited", body);
+}

+ 17 - 1
src2/main.c

@@ -34,6 +34,7 @@ int main(int argc, char** argv) {
 #endif
 
     bool profile = false;
+    bool debug = false;
     const char* filename = NULL;
 
     for(int i = 1; i < argc; i++) {
@@ -41,11 +42,20 @@ int main(int argc, char** argv) {
             profile = true;
             continue;
         }
+        if(strcmp(argv[i], "--debug") == 0) {
+            debug = true;
+            continue;
+        }
         if(filename == NULL) {
             filename = argv[i];
             continue;
         }
-        printf("Usage: pocketpy [--profile] filename\n");
+        printf("Usage: pocketpy [--profile] [--debug] filename\n");
+    }
+
+    if(debug && profile) {
+        printf("Error: --debug and --profile cannot be used together.\n");
+        return 1;
     }
 
     py_initialize();
@@ -53,6 +63,8 @@ int main(int argc, char** argv) {
 
     if(filename == NULL) {
         if(profile) printf("Warning: --profile is ignored in REPL mode.\n");
+        if(debug) printf("Warning: --debug is ignored in REPL mode.\n");
+
         printf("pocketpy " PK_VERSION " (" __DATE__ ", " __TIME__ ") ");
         printf("[%d bit] on %s", (int)(sizeof(void*) * 8), PY_SYS_PLATFORM_STRING);
 #ifndef NDEBUG
@@ -79,6 +91,8 @@ int main(int argc, char** argv) {
         }
     } else {
         if(profile) py_profiler_begin();
+        if(debug) py_debugger_waitforattach("127.0.0.1", 6110);
+
         char* source = read_file(filename);
         if(source) {
             if(!py_exec(source, filename, EXEC_MODE, NULL))
@@ -101,5 +115,7 @@ int main(int argc, char** argv) {
 
     int code = py_checkexc(false) ? 1 : 0;
     py_finalize();
+
+    if(debug) py_debugger_exit(code);
     return code;
 }