vp2pydap 3.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.3
2
+ Name: vp2pydap
3
+ Version: 3.0.0
4
+ Summary: An implementation of a Varphi-to-DAP-compliant-Python compiler.
5
+ Author: Varphi
6
+ Author-email: Varphi <support@varphi-lang.com>
7
+ Requires-Dist: vp2py>=3.0.0
8
+ Requires-Dist: varphi-devkit>=3.0.0
9
+ Requires-Dist: typer
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+
13
+ # The Varphi-to-PythonDAP Compiler
14
+ This is the source code repository for a compiler that converts the [Varphi programming language](https://varphi-lang.com/) into a Debug Adapter Protocol (DAP) debugger written in Python.
@@ -0,0 +1,2 @@
1
+ # The Varphi-to-PythonDAP Compiler
2
+ This is the source code repository for a compiler that converts the [Varphi programming language](https://varphi-lang.com/) into a Debug Adapter Protocol (DAP) debugger written in Python.
@@ -0,0 +1,29 @@
1
+ [project]
2
+ name = "vp2pydap"
3
+ version = "3.0.0"
4
+ description = "An implementation of a Varphi-to-DAP-compliant-Python compiler."
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ dependencies = [
8
+ "vp2py>=3.0.0",
9
+ "varphi-devkit>=3.0.0",
10
+ "typer",
11
+ ]
12
+
13
+ [[project.authors]]
14
+ name = "Varphi"
15
+ email = "support@varphi-lang.com"
16
+
17
+ [project.scripts]
18
+ vp2pydap = "vp2pydap.cli:main"
19
+
20
+ [build-system]
21
+ requires = ["uv_build>=0.9.18,<0.10.0"]
22
+ build-backend = "uv_build"
23
+
24
+ [dependency-groups]
25
+ dev = [
26
+ "black",
27
+ "pytest",
28
+ "vermin",
29
+ ]
@@ -0,0 +1,28 @@
1
+ [project]
2
+ name = "vp2pydap"
3
+ version = "3.0.0"
4
+ description = "An implementation of a Varphi-to-DAP-compliant-Python compiler."
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Varphi", email = "support@varphi-lang.com" }
8
+ ]
9
+ requires-python = ">=3.10"
10
+ dependencies = [
11
+ "vp2py>=3.0.0",
12
+ "varphi-devkit>=3.0.0",
13
+ "typer",
14
+ ]
15
+
16
+ [project.scripts]
17
+ vp2pydap = "vp2pydap.cli:main"
18
+
19
+ [build-system]
20
+ requires = ["uv_build>=0.9.18,<0.10.0"]
21
+ build-backend = "uv_build"
22
+
23
+ [dependency-groups]
24
+ dev = [
25
+ "black",
26
+ "pytest",
27
+ "vermin"
28
+ ]
@@ -0,0 +1,3 @@
1
+ from .compiler import VarphiToPythonDAPCompiler
2
+
3
+ __all__ = ["VarphiToPythonDAPCompiler"]
@@ -0,0 +1,36 @@
1
+ from pathlib import Path
2
+ import typer
3
+
4
+
5
+ def vp2pydap(
6
+ input_file: Path = typer.Argument(
7
+ ...,
8
+ exists=True,
9
+ file_okay=True,
10
+ dir_okay=False,
11
+ readable=True,
12
+ resolve_path=True,
13
+ help="Path to input Varphi source file",
14
+ ),
15
+ ):
16
+ """
17
+ Compile a Varphi source code file to a Python Debug Adapter Protocol (DAP) server.
18
+
19
+ The output of this command is a Python script that, when run, listens
20
+ for DAP JSON-RPC messages on stdin/stdout.
21
+ """
22
+ from .compiler import VarphiToPythonDAPCompiler
23
+
24
+ compiler = VarphiToPythonDAPCompiler()
25
+ compiler.set_source_path(str(input_file))
26
+ source_code = input_file.read_text(encoding="utf-8")
27
+ compiled_code = compiler.compile(source_code)
28
+ typer.echo(compiled_code)
29
+
30
+
31
+ def main():
32
+ typer.run(vp2pydap)
33
+
34
+
35
+ if __name__ == "__main__":
36
+ main()
@@ -0,0 +1,111 @@
1
+ import os
2
+ from varphi_devkit import (
3
+ VarphiCompiler,
4
+ BuiltinSymbol,
5
+ Variable,
6
+ Direction,
7
+ Character,
8
+ ReadWriteTupleElement,
9
+ )
10
+
11
+ TEMPLATE = """\
12
+ import argparse
13
+ from vp2py.lib import State
14
+ from varphi_devkit import BuiltinSymbol, Direction, Variable, Character, VarphiTransition
15
+ from vp2pydap.lib import DAPServer
16
+
17
+ # --- State Registry ---
18
+ state_registry = {{
19
+ {state_registry_entries}
20
+ }}
21
+
22
+ # --- Transition Definitions ---
23
+ {instruction_definitions}
24
+
25
+ # --- Runtime Setup ---
26
+ initial_state = state_registry['{initial_state}']
27
+ k = {num_tapes}
28
+ ORIGINAL_SOURCE_PATH = "{original_source_path}"
29
+
30
+ if __name__ == "__main__":
31
+ parser = argparse.ArgumentParser()
32
+ parser.add_argument('--tapes', nargs='*', help='Initial values for tapes', default=[])
33
+ args, unknown = parser.parse_known_args()
34
+
35
+ input_tapes = args.tapes
36
+ while len(input_tapes) < k:
37
+ input_tapes.append("")
38
+
39
+ server = DAPServer(k, initial_state, state_registry, input_tapes, ORIGINAL_SOURCE_PATH)
40
+ server.run_event_loop()
41
+ """
42
+
43
+
44
+ class VarphiToPythonDAPCompiler(VarphiCompiler):
45
+ _source_path: str
46
+
47
+ def __init__(self):
48
+ super().__init__()
49
+ self._source_path = "unknown.vp"
50
+
51
+ def set_source_path(self, path: str) -> None:
52
+ """
53
+ Sets the path of the source file being compiled.
54
+ This path is embedded into the generated debugger to support source mapping.
55
+ """
56
+ self._source_path = path
57
+
58
+ def _format_symbol(self, s: ReadWriteTupleElement) -> str:
59
+ if s == BuiltinSymbol.BLANK:
60
+ return "BuiltinSymbol.BLANK"
61
+ if isinstance(s, Variable):
62
+ return f"Variable({s.id})"
63
+ if isinstance(s, Character):
64
+ return f"Character({repr(s.value)})"
65
+ raise ValueError(f"Unknown symbol type: {type(s)}")
66
+
67
+ def _format_direction(self, d: Direction) -> str:
68
+ return f"Direction.{d.name}"
69
+
70
+ def _generate_compiled_program(self) -> str:
71
+ all_states = self.states
72
+
73
+ registry_entries = "\n".join(
74
+ f" '{name}': State('{name}')," for name in all_states
75
+ )
76
+
77
+ instructions_code = []
78
+ for transitions in self.ir.values():
79
+ for t in transitions:
80
+ read_str = "(" + ", ".join(self._format_symbol(s) for s in t.read_symbols)
81
+ read_str += ",)" if len(t.read_symbols) == 1 else ")"
82
+
83
+ write_str = "(" + ", ".join(self._format_symbol(s) for s in t.write_symbols)
84
+ write_str += ",)" if len(t.write_symbols) == 1 else ")"
85
+
86
+ shift_str = "(" + ", ".join(self._format_direction(d) for d in t.shift_directions)
87
+ shift_str += ",)" if len(t.shift_directions) == 1 else ")"
88
+
89
+ code = (
90
+ f"state_registry['{t.current_state}'].add_transition(\n"
91
+ f" VarphiTransition(\n"
92
+ f" current_state='{t.current_state}',\n"
93
+ f" read_symbols={read_str},\n"
94
+ f" next_state='{t.next_state}',\n"
95
+ f" write_symbols={write_str},\n"
96
+ f" shift_directions={shift_str},\n"
97
+ f" line_number={t.line_number}\n"
98
+ f" )\n"
99
+ f")"
100
+ )
101
+ instructions_code.append(code)
102
+
103
+ sanitized_path = os.path.abspath(self._source_path).replace("\\", "\\\\")
104
+
105
+ return TEMPLATE.format(
106
+ state_registry_entries=registry_entries,
107
+ instruction_definitions="\n\n".join(instructions_code),
108
+ initial_state=self.initial_state,
109
+ num_tapes=self._tape_count,
110
+ original_source_path=sanitized_path,
111
+ )
@@ -0,0 +1,3 @@
1
+ from .debugger import DAPServer
2
+
3
+ __all__ = ["DAPServer"]
@@ -0,0 +1,419 @@
1
+ from __future__ import annotations
2
+ import sys
3
+ import json
4
+ import threading
5
+ import queue
6
+ import os
7
+ import io
8
+ from typing import Dict, Any, List, Optional
9
+ from vp2py.lib import TuringMachine, State, Tape
10
+ from varphi_devkit import BuiltinSymbol, Character
11
+
12
+
13
+ class DAPStdout(io.TextIOBase):
14
+ """Redirects stdout to the VS Code Debug Console (Output category)."""
15
+ server: DAPServer
16
+ buffer: io.StringIO
17
+
18
+ def __init__(self, server_instance):
19
+ self.server = server_instance
20
+ self.buffer = io.StringIO()
21
+
22
+ def write(self, s: str):
23
+ if self.server:
24
+ self.server._send_event("output", {"category": "stdout", "output": s})
25
+ return len(s)
26
+
27
+
28
+ class DAPStderr(io.TextIOBase):
29
+ """Redirects stderr to the VS Code Debug Console (Stderr category, usually red)."""
30
+ server: DAPServer
31
+ buffer: io.StringIO
32
+
33
+ def __init__(self, server_instance):
34
+ self.server = server_instance
35
+ self.buffer = io.StringIO()
36
+
37
+ def write(self, s: str):
38
+ if self.server:
39
+ self.server._send_event("output", {"category": "stderr", "output": s})
40
+ return len(s)
41
+
42
+
43
+ class DAPServer:
44
+ tm: TuringMachine
45
+ breakpoints: dict[int, bool]
46
+ running: bool
47
+ step_granularity: Optional[str]
48
+ steps_count: int
49
+ input_queue: queue.Queue
50
+ original_source_path: str
51
+ blank_char: str
52
+
53
+ _seq: int = 0
54
+ _write_lock: threading.Lock
55
+
56
+ def __init__(
57
+ self,
58
+ k: int,
59
+ initial_state: State,
60
+ state_registry: dict[str, State],
61
+ input_tapes: List[str],
62
+ original_source_path: str,
63
+ blank_char: str = "_",
64
+ ):
65
+ self.original_source_path = os.path.abspath(original_source_path)
66
+ self._write_lock = threading.Lock()
67
+ self.blank_char = blank_char
68
+
69
+ parsed_tapes = []
70
+ for t_str in input_tapes:
71
+ parsed_tape = []
72
+ for char in t_str:
73
+ if char == self.blank_char:
74
+ parsed_tape.append(BuiltinSymbol.BLANK)
75
+ else:
76
+ parsed_tape.append(Character(char))
77
+ parsed_tapes.append(Tape(parsed_tape))
78
+
79
+ self.tm = TuringMachine(k, tuple(parsed_tapes), initial_state, state_registry)
80
+
81
+ self.breakpoints = {}
82
+ self.running = False
83
+ self.step_granularity = "instruction"
84
+ self.steps_count = 0
85
+
86
+ self.input_queue = queue.Queue()
87
+
88
+ # Redirect stdout and stderr to DAP console
89
+ sys.stdout = DAPStdout(self)
90
+ sys.stderr = DAPStderr(self)
91
+
92
+ self.reader_thread = threading.Thread(target=self._read_input_loop, daemon=True)
93
+ self.reader_thread.start()
94
+
95
+ # Initialize the machine view
96
+ try:
97
+ self.tm.peek()
98
+ except Exception:
99
+ # If peek fails immediately (e.g. invalid initial state), we catch it later or let it slide
100
+ pass
101
+
102
+ def _format_cell(self, val: Character | BuiltinSymbol) -> str:
103
+ return self.blank_char if val == BuiltinSymbol.BLANK else val.value
104
+
105
+ def _send_message(self, msg: Dict[str, Any]):
106
+ """Sends a DAP-compliant message to the real stdout."""
107
+ with self._write_lock:
108
+ self._seq += 1
109
+ msg["seq"] = self._seq
110
+ json_msg = json.dumps(msg)
111
+ encoded = json_msg.encode("utf-8")
112
+ # We write to __stdout__ because sys.stdout is redirected
113
+ sys.__stdout__.buffer.write(
114
+ f"Content-Length: {len(encoded)}\r\n\r\n".encode("utf-8")
115
+ )
116
+ sys.__stdout__.buffer.write(encoded)
117
+ sys.__stdout__.buffer.flush()
118
+
119
+ def _read_input_loop(self):
120
+ buffer = sys.stdin.buffer
121
+ while True:
122
+ try:
123
+ content_length = 0
124
+ while True:
125
+ line = buffer.readline()
126
+ if not line:
127
+ return
128
+
129
+ line_str = line.decode("utf-8").strip()
130
+ if not line_str:
131
+ break
132
+
133
+ if line_str.startswith("Content-Length:"):
134
+ content_length = int(line_str.split(":", 1)[1].strip())
135
+
136
+ if content_length > 0:
137
+ content = buffer.read(content_length)
138
+ self.input_queue.put(json.loads(content))
139
+ except Exception:
140
+ break
141
+
142
+ def run_event_loop(self):
143
+ while True:
144
+ try:
145
+ timeout = 0.0 if self.running else None
146
+ req = self.input_queue.get(timeout=timeout)
147
+ self._handle_request(req)
148
+ except queue.Empty:
149
+ pass
150
+
151
+ if self.running:
152
+ self._step_machine()
153
+
154
+ def _print_halt_report(self):
155
+ """Calculates statistics and prints the halt report to the Debug Console."""
156
+ total_space = sum(h.space_complexity() for h in self.tm.heads)
157
+
158
+ report = []
159
+ report.append(f"HALTED at state '{self.tm.state.name}'")
160
+ report.append(f"Time taken: {self.steps_count} steps")
161
+ report.append(f"Space used: {total_space} cells")
162
+ report.append(f"Number of tapes: {len(self.tm.heads)}")
163
+
164
+ for i, tape in enumerate(self.tm.tapes):
165
+ tape_dict = tape._tape
166
+ if not tape_dict:
167
+ content = ""
168
+ else:
169
+ min_idx = min(tape_dict.keys())
170
+ max_idx = max(tape_dict.keys())
171
+ content = "".join(self._format_cell(tape_dict.get(k, BuiltinSymbol.BLANK)) for k in range(min_idx, max_idx + 1))
172
+
173
+ report.append(f"Tape {i + 1}: {content.strip(self.blank_char)}")
174
+
175
+ # Join with newlines and print. This goes to DAPStdout -> VS Code Debug Console
176
+ print("\n" + "\n".join(report) + "\n")
177
+
178
+ def _step_machine(self):
179
+ # Check termination
180
+ if self.tm._next_transition is None:
181
+ self.running = False
182
+ self._print_halt_report()
183
+ self._send_event("terminated")
184
+ self._send_event("exited", {"exitCode": 0})
185
+ return
186
+
187
+ # Check breakpoints (unless single-stepping)
188
+ if self.step_granularity != "step":
189
+ current_line = self.tm._next_transition.line_number
190
+ if self.breakpoints.get(current_line, False):
191
+ self.running = False
192
+ self._send_event(
193
+ "stopped",
194
+ {"reason": "breakpoint", "threadId": 1, "allThreadsStopped": True},
195
+ )
196
+ return
197
+
198
+ # Execute step
199
+ try:
200
+ self.tm.step()
201
+ self.steps_count += 1
202
+ has_next = self.tm.peek()
203
+ except Exception as e:
204
+ self.running = False
205
+ # Print error to debug console
206
+ sys.stderr.write(f"\nRUNTIME ERROR: {str(e)}\n")
207
+ # Notify VS Code to pause on exception
208
+ self._send_event("stopped", {
209
+ "reason": "exception",
210
+ "description": "Paused on exception",
211
+ "text": str(e),
212
+ "threadId": 1
213
+ })
214
+ return
215
+
216
+ # Handle step pause
217
+ if self.step_granularity == "step":
218
+ self.running = False
219
+ self.step_granularity = None
220
+ reason = "step" if has_next else "pause"
221
+ self._send_event("stopped", {"reason": reason, "threadId": 1})
222
+
223
+ def _send_response(
224
+ self, req: Dict, success: bool, body: Any = None, message: str = None
225
+ ):
226
+ resp = {
227
+ "type": "response",
228
+ "request_seq": req["seq"],
229
+ "command": req["command"],
230
+ "success": success,
231
+ }
232
+ if body:
233
+ resp["body"] = body
234
+ if message:
235
+ resp["message"] = message
236
+ self._send_message(resp)
237
+
238
+ def _send_event(self, event: str, body: Any = None):
239
+ msg = {"type": "event", "event": event}
240
+ if body:
241
+ msg["body"] = body
242
+ self._send_message(msg)
243
+
244
+ def _handle_request(self, req: Dict[str, Any]):
245
+ try:
246
+ if req["type"] == "request":
247
+ cmd = req["command"]
248
+ handler = getattr(self, f"handle_{cmd}", None)
249
+ if handler:
250
+ handler(req)
251
+ else:
252
+ self._send_response(
253
+ req, False, message=f"Command '{cmd}' not implemented"
254
+ )
255
+ except Exception as e:
256
+ self._send_response(req, False, message=str(e))
257
+
258
+ # --- Handlers ---
259
+
260
+ def handle_initialize(self, req):
261
+ self._send_response(
262
+ req,
263
+ True,
264
+ {
265
+ "supportsConfigurationDoneRequest": True,
266
+ "supportsSetVariable": False,
267
+ "supportsValueFormattingOptions": False,
268
+ "supportsExceptionInfoRequest": True,
269
+ },
270
+ )
271
+ self._send_event("initialized")
272
+
273
+ def handle_launch(self, req):
274
+ self._send_response(req, True)
275
+
276
+ def handle_setBreakpoints(self, req):
277
+ lines = req["arguments"].get("lines", [])
278
+ self.breakpoints = {ln: True for ln in lines}
279
+ verified_bps = [{"verified": True, "line": ln} for ln in lines]
280
+ self._send_response(req, True, {"breakpoints": verified_bps})
281
+
282
+ def handle_configurationDone(self, req):
283
+ self._send_response(req, True)
284
+ self._send_event("stopped", {"reason": "entry", "threadId": 1})
285
+
286
+ def handle_threads(self, req):
287
+ self._send_response(req, True, {"threads": [{"id": 1, "name": "Main Thread"}]})
288
+
289
+ def handle_stackTrace(self, req):
290
+ if self.tm._next_transition:
291
+ line = self.tm._next_transition.line_number
292
+ name = f"State: {self.tm.state.name}"
293
+ else:
294
+ line = 0
295
+ name = f"HALTED (State: {self.tm.state.name})"
296
+
297
+ self._send_response(
298
+ req,
299
+ True,
300
+ {
301
+ "stackFrames": [
302
+ {
303
+ "id": 1,
304
+ "name": name,
305
+ "line": line,
306
+ "column": 1,
307
+ "source": {
308
+ "name": os.path.basename(self.original_source_path),
309
+ "path": self.original_source_path,
310
+ "sourceReference": 0,
311
+ },
312
+ "presentationHint": "normal",
313
+ }
314
+ ],
315
+ "totalFrames": 1,
316
+ },
317
+ )
318
+
319
+ def handle_scopes(self, req):
320
+ self._send_response(
321
+ req,
322
+ True,
323
+ {
324
+ "scopes": [
325
+ {
326
+ "name": "Machine State",
327
+ "variablesReference": 1,
328
+ "expensive": False,
329
+ }
330
+ ]
331
+ },
332
+ )
333
+
334
+ def handle_variables(self, req):
335
+ vars_list = []
336
+
337
+ # Machine metrics
338
+ vars_list.append(
339
+ {
340
+ "name": "Current State",
341
+ "value": str(self.tm.state.name),
342
+ "type": "string",
343
+ "variablesReference": 0,
344
+ }
345
+ )
346
+ vars_list.append(
347
+ {
348
+ "name": "Time taken",
349
+ "value": str(self.steps_count),
350
+ "type": "integer",
351
+ "variablesReference": 0,
352
+ }
353
+ )
354
+
355
+ total_space = sum(h.space_complexity() for h in self.tm.heads)
356
+ vars_list.append(
357
+ {
358
+ "name": "Space used",
359
+ "value": str(total_space),
360
+ "type": "integer",
361
+ "variablesReference": 0,
362
+ }
363
+ )
364
+
365
+ # Tape visualizations
366
+ for i, head in enumerate(self.tm.heads):
367
+ center = head.index
368
+ raw_dict = head.tape._tape
369
+
370
+ left_ctx = "".join(
371
+ self._format_cell(raw_dict.get(x, BuiltinSymbol.BLANK)) for x in range(center - 5, center)
372
+ )
373
+ curr_val = self._format_cell(raw_dict.get(center, BuiltinSymbol.BLANK))
374
+ right_ctx = "".join(
375
+ self._format_cell(raw_dict.get(x, BuiltinSymbol.BLANK)) for x in range(center + 1, center + 6)
376
+ )
377
+
378
+ tape_display = f"{left_ctx}[{curr_val}]{right_ctx}"
379
+
380
+ vars_list.append(
381
+ {
382
+ "name": f"Tape {i + 1}",
383
+ "value": tape_display,
384
+ "type": "string",
385
+ "variablesReference": 0,
386
+ }
387
+ )
388
+
389
+ self._send_response(req, True, {"variables": vars_list})
390
+
391
+ def handle_next(self, req):
392
+ self.step_granularity = "step"
393
+ self.running = True
394
+ self._send_response(req, True)
395
+
396
+ def handle_stepIn(self, req):
397
+ self.handle_next(req)
398
+
399
+ def handle_continue(self, req):
400
+ self.step_granularity = None
401
+ self.running = True
402
+ self._send_response(req, True)
403
+
404
+ def handle_pause(self, req):
405
+ self.running = False
406
+ self._send_response(req, True)
407
+ self._send_event("stopped", {"reason": "pause", "threadId": 1})
408
+
409
+ def handle_disconnect(self, req):
410
+ self.running = False
411
+ self._send_response(req, True)
412
+ sys.exit(0)
413
+
414
+ def handle_exceptionInfo(self, req):
415
+ self._send_response(req, True, {
416
+ "exceptionId": "runtime_error",
417
+ "description": "An error occurred during execution",
418
+ "breakMode": "always"
419
+ })