1use serde_json::{json, Value};
17
18use logicaffeine_compile::debug_session::DebugSession;
19
20pub struct DapServer {
23 session: Option<DebugSession>,
24 out_seq: i64,
25 done: bool,
27}
28
29impl Default for DapServer {
30 fn default() -> Self {
31 Self::new()
32 }
33}
34
35impl DapServer {
36 pub fn new() -> DapServer {
38 DapServer {
39 session: None,
40 out_seq: 0,
41 done: false,
42 }
43 }
44
45 pub fn is_done(&self) -> bool {
47 self.done
48 }
49
50 fn next_seq(&mut self) -> i64 {
52 self.out_seq += 1;
53 self.out_seq
54 }
55
56 fn response(&mut self, request: &Value, success: bool, body: Value) -> Value {
58 let request_seq = request.get("seq").and_then(Value::as_i64).unwrap_or(0);
59 let command = request.get("command").and_then(Value::as_str).unwrap_or("");
60 json!({
61 "seq": self.next_seq(),
62 "type": "response",
63 "request_seq": request_seq,
64 "success": success,
65 "command": command,
66 "body": body,
67 })
68 }
69
70 fn error(&mut self, request: &Value, message: &str) -> Value {
72 let request_seq = request.get("seq").and_then(Value::as_i64).unwrap_or(0);
73 let command = request.get("command").and_then(Value::as_str).unwrap_or("");
74 json!({
75 "seq": self.next_seq(),
76 "type": "response",
77 "request_seq": request_seq,
78 "success": false,
79 "command": command,
80 "message": message,
81 })
82 }
83
84 fn event(&mut self, name: &str, body: Value) -> Value {
86 json!({
87 "seq": self.next_seq(),
88 "type": "event",
89 "event": name,
90 "body": body,
91 })
92 }
93
94 fn stop_or_terminate(&mut self, reason: &str) -> Value {
97 let running = self
98 .session
99 .as_ref()
100 .map(DebugSession::is_running)
101 .unwrap_or(false);
102 if running {
103 self.event(
104 "stopped",
105 json!({ "reason": reason, "threadId": 1, "allThreadsStopped": true }),
106 )
107 } else {
108 self.event("terminated", json!({}))
109 }
110 }
111
112 pub fn handle(&mut self, request: &Value) -> Vec<Value> {
115 let command = request.get("command").and_then(Value::as_str).unwrap_or("");
116 match command {
117 "initialize" => {
118 let resp = self.response(
119 request,
120 true,
121 json!({
122 "supportsStepBack": true,
123 "supportsConfigurationDoneRequest": true,
124 "supportsEvaluateForHovers": true,
125 "supportsTerminateRequest": true,
126 }),
127 );
128 let initialized = self.event("initialized", json!({}));
129 vec![resp, initialized]
130 }
131
132 "launch" => {
133 let args = request.get("arguments").cloned().unwrap_or(Value::Null);
134 let source = launch_source(&args);
135 match source {
136 Err(msg) => vec![self.error(request, &msg)],
137 Ok(src) => match DebugSession::launch(&src) {
138 Err(e) => vec![self.error(request, &e)],
139 Ok(session) => {
140 self.session = Some(session);
141 let resp = self.response(request, true, json!({}));
142 let stopped = self.event(
143 "stopped",
144 json!({ "reason": "entry", "threadId": 1, "allThreadsStopped": true }),
145 );
146 vec![resp, stopped]
147 }
148 },
149 }
150 }
151
152 "setBreakpoints" => {
153 let lines = request
158 .get("arguments")
159 .and_then(|a| a.get("breakpoints"))
160 .and_then(Value::as_array)
161 .cloned()
162 .unwrap_or_default();
163 let verified: Vec<Value> = lines
164 .iter()
165 .map(|bp| {
166 let line = bp.get("line").and_then(Value::as_i64).unwrap_or(0);
167 json!({ "verified": true, "line": line })
168 })
169 .collect();
170 vec![self.response(request, true, json!({ "breakpoints": verified }))]
171 }
172
173 "configurationDone" => vec![self.response(request, true, json!({}))],
174
175 "threads" => {
176 let threads = match &self.session {
177 None => Vec::new(),
178 Some(s) => s
179 .threads()
180 .into_iter()
181 .map(|t| json!({ "id": t.id, "name": t.name }))
182 .collect(),
183 };
184 vec![self.response(request, true, json!({ "threads": threads }))]
185 }
186
187 "stackTrace" => {
188 let frames = match &self.session {
189 None => Vec::new(),
190 Some(s) => s
191 .stack_trace()
192 .into_iter()
193 .map(|f| json!({ "id": f.id, "name": f.name, "line": f.line, "column": 0 }))
194 .collect::<Vec<_>>(),
195 };
196 let total = frames.len();
197 vec![self.response(
198 request,
199 true,
200 json!({ "stackFrames": frames, "totalFrames": total }),
201 )]
202 }
203
204 "scopes" => {
205 let frame_id = request
208 .get("arguments")
209 .and_then(|a| a.get("frameId"))
210 .and_then(Value::as_i64)
211 .unwrap_or(0);
212 vec![self.response(
213 request,
214 true,
215 json!({ "scopes": [{
216 "name": "Locals",
217 "variablesReference": frame_id + 1,
218 "expensive": false,
219 }] }),
220 )]
221 }
222
223 "variables" => {
224 let vref = request
225 .get("arguments")
226 .and_then(|a| a.get("variablesReference"))
227 .and_then(Value::as_i64)
228 .unwrap_or(0);
229 let frame_id = vref - 1;
230 let vars = match (&self.session, frame_id >= 0) {
231 (Some(s), true) => s
232 .variables(frame_id)
233 .into_iter()
234 .map(|v| {
235 json!({
236 "name": v.name,
237 "value": v.value,
238 "type": v.kind,
239 "variablesReference": 0,
240 })
241 })
242 .collect::<Vec<_>>(),
243 _ => Vec::new(),
244 };
245 vec![self.response(request, true, json!({ "variables": vars }))]
246 }
247
248 "next" | "stepIn" | "stepOut" | "continue" | "stepBack" | "reverseContinue" => {
249 if self.session.is_none() {
250 return vec![self.error(request, "no active session — send launch first")];
251 }
252 {
253 let s = self.session.as_mut().unwrap();
254 match command {
255 "next" => s.next(),
256 "stepIn" => s.step_in(),
257 "stepOut" => s.step_out(),
258 "continue" => s.continue_(),
259 "stepBack" => s.step_back(),
260 "reverseContinue" => s.reverse_continue(),
261 _ => unreachable!("outer match guarantees a stepping command"),
262 }
263 }
264 let body = if command == "continue" {
265 json!({ "allThreadsContinued": true })
266 } else {
267 json!({})
268 };
269 let resp = self.response(request, true, body);
270 let evt = self.stop_or_terminate("step");
271 vec![resp, evt]
272 }
273
274 "evaluate" => {
275 let expr = request
276 .get("arguments")
277 .and_then(|a| a.get("expression"))
278 .and_then(Value::as_str)
279 .unwrap_or("");
280 match &self.session {
281 None => vec![self.error(request, "no active session")],
282 Some(s) => match s.evaluate(expr) {
283 Ok(result) => vec![self.response(
284 request,
285 true,
286 json!({ "result": result.trim_end(), "variablesReference": 0 }),
287 )],
288 Err(e) => vec![self.error(request, &e)],
289 },
290 }
291 }
292
293 "disconnect" | "terminate" => {
294 self.done = true;
295 vec![self.response(request, true, json!({}))]
296 }
297
298 other => vec![self.error(request, &format!("unsupported DAP command '{other}'"))],
299 }
300 }
301}
302
303fn launch_source(args: &Value) -> Result<String, String> {
306 if let Some(src) = args.get("source").and_then(Value::as_str) {
307 return Ok(src.to_string());
308 }
309 if let Some(path) = args.get("program").and_then(Value::as_str) {
310 return std::fs::read_to_string(path)
311 .map_err(|e| format!("cannot read program '{path}': {e}"));
312 }
313 Err("launch needs a `source` (inline LOGOS) or a `program` (file path)".to_string())
314}
315
316pub fn cmd_dap() -> Result<(), Box<dyn std::error::Error>> {
319 use std::io::Write;
320
321 let stdin = std::io::stdin();
322 let stdout = std::io::stdout();
323 let mut server = DapServer::new();
324 let mut reader = std::io::BufReader::new(stdin.lock());
325
326 while let Some(request) = read_message(&mut reader)? {
327 let outs = server.handle(&request);
328 let mut out = stdout.lock();
329 for msg in outs {
330 write_message(&mut out, &msg)?;
331 }
332 out.flush()?;
333 if server.is_done() {
334 break;
335 }
336 }
337 Ok(())
338}
339
340fn read_message<R: std::io::BufRead>(reader: &mut R) -> std::io::Result<Option<Value>> {
342 let mut content_length: Option<usize> = None;
343 loop {
344 let mut line = String::new();
345 let n = reader.read_line(&mut line)?;
346 if n == 0 {
347 return Ok(None); }
349 let trimmed = line.trim_end_matches(['\r', '\n']);
350 if trimmed.is_empty() {
351 break; }
353 if let Some(rest) = trimmed.strip_prefix("Content-Length:") {
354 content_length = rest.trim().parse().ok();
355 }
356 }
357 let len = match content_length {
358 Some(l) => l,
359 None => return Ok(None),
360 };
361 let mut buf = vec![0u8; len];
362 reader.read_exact(&mut buf)?;
363 match serde_json::from_slice(&buf) {
364 Ok(v) => Ok(Some(v)),
365 Err(_) => Ok(Some(Value::Null)),
366 }
367}
368
369fn write_message<W: std::io::Write>(out: &mut W, msg: &Value) -> std::io::Result<()> {
371 let body = serde_json::to_vec(msg)?;
372 write!(out, "Content-Length: {}\r\n\r\n", body.len())?;
373 out.write_all(&body)?;
374 Ok(())
375}