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.
- vp2pydap-3.0.0/PKG-INFO +14 -0
- vp2pydap-3.0.0/README.md +2 -0
- vp2pydap-3.0.0/pyproject.toml +29 -0
- vp2pydap-3.0.0/pyproject.toml.orig +28 -0
- vp2pydap-3.0.0/src/vp2pydap/__init__.py +3 -0
- vp2pydap-3.0.0/src/vp2pydap/cli.py +36 -0
- vp2pydap-3.0.0/src/vp2pydap/compiler.py +111 -0
- vp2pydap-3.0.0/src/vp2pydap/lib/__init__.py +3 -0
- vp2pydap-3.0.0/src/vp2pydap/lib/debugger.py +419 -0
vp2pydap-3.0.0/PKG-INFO
ADDED
|
@@ -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.
|
vp2pydap-3.0.0/README.md
ADDED
|
@@ -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,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,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
|
+
})
|