qubasic 0.3.0__tar.gz → 0.3.1__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.
- {qubasic-0.3.0 → qubasic-0.3.1}/CHANGELOG.md +13 -0
- {qubasic-0.3.0/qubasic.egg-info → qubasic-0.3.1}/PKG-INFO +1 -1
- {qubasic-0.3.0 → qubasic-0.3.1}/pyproject.toml +2 -2
- {qubasic-0.3.0 → qubasic-0.3.1/qubasic.egg-info}/PKG-INFO +1 -1
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/__init__.py +1 -1
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/classic.py +1 -0
- qubasic-0.3.1/qubasic_core/control_flow.py +309 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/debug.py +31 -6
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/display.py +4 -2
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/executor.py +14 -278
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/expression.py +93 -15
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/file_io.py +18 -9
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/help_text.py +1 -3
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/locc_commands.py +0 -35
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/locc_execution.py +6 -2
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/memory.py +34 -19
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/profiler.py +49 -5
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/program_mgmt.py +15 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/subs.py +69 -54
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/sweep.py +2 -1
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/terminal.py +66 -9
- qubasic-0.3.0/qubasic_core/control_flow.py +0 -576
- {qubasic-0.3.0 → qubasic-0.3.1}/LICENSE +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/MANIFEST.in +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/README.md +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/examples/bell.qb +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/examples/grover3.qb +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/examples/locc_teleport.qb +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/examples/sweep_rx.qb +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic.egg-info/SOURCES.txt +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic.egg-info/dependency_links.txt +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic.egg-info/entry_points.txt +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic.egg-info/requires.txt +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic.egg-info/top_level.txt +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic.py +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/__main__.py +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/analysis.py +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/backend.py +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/demos.py +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/engine.py +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/engine_state.py +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/errors.py +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/exec_context.py +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/gates.py +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/io_protocol.py +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/locc.py +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/locc_display.py +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/locc_engine.py +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/mock_backend.py +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/noise_mixin.py +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/parser.py +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/patterns.py +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/protocol.py +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/scope.py +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/screen.py +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/state_display.py +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/statements.py +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/qubasic_core/strings.py +0 -0
- {qubasic-0.3.0 → qubasic-0.3.1}/setup.cfg +0 -0
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.1 (2026-03-29)
|
|
4
|
+
|
|
5
|
+
- Fix f-string backslash escapes that broke import on Python 3.10/3.11
|
|
6
|
+
|
|
7
|
+
## 0.3.0 (2026-03-28)
|
|
8
|
+
|
|
9
|
+
- FUNCTION return value fix, APPLY_CIRCUIT in programs, stabilizer fallback
|
|
10
|
+
- Bump to 0.3.0
|
|
11
|
+
|
|
12
|
+
## 0.2.0 (2026-03-28)
|
|
13
|
+
|
|
14
|
+
- Rename qbasic -> qubasic everywhere (PyPI name conflict)
|
|
15
|
+
|
|
3
16
|
## 0.1.0 (2026-03-28)
|
|
4
17
|
|
|
5
18
|
Initial PyPI release.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "qubasic"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.1"
|
|
8
8
|
description = "Quantum BASIC Interactive Terminal"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -65,7 +65,7 @@ skip_empty = true
|
|
|
65
65
|
python_version = "3.10"
|
|
66
66
|
warn_return_any = true
|
|
67
67
|
warn_unused_configs = true
|
|
68
|
-
disallow_untyped_defs =
|
|
68
|
+
disallow_untyped_defs = true
|
|
69
69
|
check_untyped_defs = true
|
|
70
70
|
warn_redundant_casts = true
|
|
71
71
|
warn_unused_ignores = true
|
|
@@ -409,6 +409,7 @@ class ClassicMixin:
|
|
|
409
409
|
# ── OPTION BASE ───────────────────────────────────────────────────
|
|
410
410
|
|
|
411
411
|
def _cf_option_base(self, stmt: str, *, parsed=None) -> tuple[bool, ExecOutcome] | None:
|
|
412
|
+
# Parsed and stored; array indexing always starts at 0 in this implementation.
|
|
412
413
|
if parsed is not None:
|
|
413
414
|
self._option_base = parsed.base
|
|
414
415
|
else:
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"""Control flow helpers extracted from QBasicTerminal.
|
|
2
|
+
|
|
3
|
+
Requires: TerminalProtocol (see qubasic_core.protocol).
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
from typing import Any, Callable
|
|
10
|
+
|
|
11
|
+
from qubasic_core.engine import ExecResult, ExecOutcome
|
|
12
|
+
from qubasic_core.parser import parse_stmt
|
|
13
|
+
from qubasic_core.statements import (
|
|
14
|
+
RawStmt, RemStmt, MeasureStmt, EndStmt, ReturnStmt, WendStmt,
|
|
15
|
+
LetArrayStmt, LetStmt, PrintStmt, GotoStmt, GosubStmt,
|
|
16
|
+
ForStmt, NextStmt, WhileStmt, IfThenStmt,
|
|
17
|
+
DataStmt, ReadStmt, OnGotoStmt, OnGosubStmt,
|
|
18
|
+
SelectCaseStmt, CaseStmt, EndSelectStmt,
|
|
19
|
+
DoStmt, LoopStmt, ExitStmt,
|
|
20
|
+
SwapStmt, DefFnStmt, OptionBaseStmt,
|
|
21
|
+
SubStmt, EndSubStmt, FunctionStmt, EndFunctionStmt, CallStmt,
|
|
22
|
+
LocalStmt, StaticStmt, SharedStmt,
|
|
23
|
+
OnErrorStmt, ResumeStmt, ErrorStmt, AssertStmt, StopStmt,
|
|
24
|
+
OnMeasureStmt, OnTimerStmt,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ControlFlowMixin:
|
|
29
|
+
"""Mixin providing control flow helpers for QBasicTerminal.
|
|
30
|
+
|
|
31
|
+
Requires: TerminalProtocol — uses self.program, self.variables,
|
|
32
|
+
self.arrays, self.locc_mode, self.locc, self._gosub_stack,
|
|
33
|
+
self._eval_with_vars(), self._eval_condition(), self._substitute_vars().
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
# ── Control flow helpers (decomposed from _exec_control_flow) ────
|
|
37
|
+
#
|
|
38
|
+
# Each _cf_* method accepts (self, stmt, parsed, ...) where parsed is
|
|
39
|
+
# a required typed Stmt object. The stmt parameter is retained in the
|
|
40
|
+
# signature for compatibility with _exec_control_flow's argument
|
|
41
|
+
# passing but is not used by the methods themselves.
|
|
42
|
+
|
|
43
|
+
def _cf_let_array(self, stmt: str, run_vars: dict[str, Any],
|
|
44
|
+
parsed: LetArrayStmt) -> tuple[bool, ExecOutcome]:
|
|
45
|
+
name, idx_expr, val_expr = parsed.name, parsed.index_expr, parsed.value_expr
|
|
46
|
+
idx = int(self._eval_with_vars(idx_expr, run_vars))
|
|
47
|
+
val = self._eval_with_vars(val_expr, run_vars)
|
|
48
|
+
if name not in self.arrays:
|
|
49
|
+
self.arrays[name] = [0.0] * (idx + 1)
|
|
50
|
+
while idx >= len(self.arrays[name]):
|
|
51
|
+
self.arrays[name].append(0.0)
|
|
52
|
+
self.arrays[name][idx] = val
|
|
53
|
+
return True, ExecResult.ADVANCE
|
|
54
|
+
|
|
55
|
+
def _cf_let_var(self, stmt: str, run_vars: dict[str, Any],
|
|
56
|
+
parsed: LetStmt) -> tuple[bool, ExecOutcome]:
|
|
57
|
+
name, expr = parsed.name, parsed.expr
|
|
58
|
+
val = self._eval_with_vars(expr, run_vars)
|
|
59
|
+
run_vars[name] = val
|
|
60
|
+
self.variables[name] = val
|
|
61
|
+
return True, ExecResult.ADVANCE
|
|
62
|
+
|
|
63
|
+
def _cf_print(self, stmt: str, run_vars: dict[str, Any],
|
|
64
|
+
parsed: PrintStmt) -> tuple[bool, ExecOutcome]:
|
|
65
|
+
raw_expr = parsed.expr
|
|
66
|
+
text = self._substitute_vars(raw_expr.strip(), run_vars)
|
|
67
|
+
# Determine trailing separator: ; suppresses newline, , advances to tab
|
|
68
|
+
suppress_newline = raw_expr.rstrip().endswith(';')
|
|
69
|
+
tab_advance = raw_expr.rstrip().endswith(',')
|
|
70
|
+
if suppress_newline:
|
|
71
|
+
text = text.rstrip().removesuffix(';').rstrip()
|
|
72
|
+
elif tab_advance:
|
|
73
|
+
text = text.rstrip().removesuffix(',').rstrip()
|
|
74
|
+
# Evaluate SPC(n) and TAB(n) inline
|
|
75
|
+
def _replace_spc(m_spc):
|
|
76
|
+
n = int(self._eval_with_vars(m_spc.group(1), run_vars))
|
|
77
|
+
return ' ' * max(0, n)
|
|
78
|
+
def _replace_tab(m_tab):
|
|
79
|
+
n = int(self._eval_with_vars(m_tab.group(1), run_vars))
|
|
80
|
+
return ' ' * max(0, n)
|
|
81
|
+
text = re.sub(r'\bSPC\s*\(([^)]+)\)', _replace_spc, text, flags=re.IGNORECASE)
|
|
82
|
+
text = re.sub(r'\bTAB\s*\(([^)]+)\)', _replace_tab, text, flags=re.IGNORECASE)
|
|
83
|
+
# Evaluate the expression
|
|
84
|
+
if (text.startswith('"') and text.endswith('"')) or \
|
|
85
|
+
(text.startswith("'") and text.endswith("'")):
|
|
86
|
+
output = text[1:-1]
|
|
87
|
+
else:
|
|
88
|
+
try:
|
|
89
|
+
ns = run_vars.as_dict() if hasattr(run_vars, 'as_dict') else dict(run_vars) if not isinstance(run_vars, dict) else run_vars
|
|
90
|
+
result = self._safe_eval(text, extra_ns=ns)
|
|
91
|
+
output = str(result)
|
|
92
|
+
except Exception:
|
|
93
|
+
output = text
|
|
94
|
+
# Output with separator behavior
|
|
95
|
+
if suppress_newline:
|
|
96
|
+
self.io.write(output)
|
|
97
|
+
elif tab_advance:
|
|
98
|
+
col = len(output) % 14
|
|
99
|
+
padding = 14 - col if col > 0 else 14
|
|
100
|
+
self.io.write(output + ' ' * padding)
|
|
101
|
+
else:
|
|
102
|
+
self.io.writeln(output)
|
|
103
|
+
return True, ExecResult.ADVANCE
|
|
104
|
+
|
|
105
|
+
def _cf_goto(self, stmt: str, sorted_lines: list[int],
|
|
106
|
+
parsed: GotoStmt) -> tuple[bool, int]:
|
|
107
|
+
target = parsed.target
|
|
108
|
+
for idx, ln in enumerate(sorted_lines):
|
|
109
|
+
if ln == target:
|
|
110
|
+
return True, idx
|
|
111
|
+
raise RuntimeError(f"GOTO {target}: LINE NOT FOUND")
|
|
112
|
+
|
|
113
|
+
def _cf_gosub(self, stmt: str, sorted_lines: list[int], ip: int,
|
|
114
|
+
parsed: GosubStmt) -> tuple[bool, int]:
|
|
115
|
+
target = parsed.target
|
|
116
|
+
self._gosub_stack.append(ip + 1)
|
|
117
|
+
for idx, ln in enumerate(sorted_lines):
|
|
118
|
+
if ln == target:
|
|
119
|
+
return True, idx
|
|
120
|
+
raise RuntimeError(f"GOSUB {target}: LINE NOT FOUND")
|
|
121
|
+
|
|
122
|
+
def _cf_for(self, stmt: str, run_vars: dict[str, Any], loop_stack: list[dict[str, Any]], ip: int,
|
|
123
|
+
parsed: ForStmt) -> tuple[bool, ExecOutcome]:
|
|
124
|
+
var = parsed.var
|
|
125
|
+
start_expr, end_expr, step_expr = parsed.start_expr, parsed.end_expr, parsed.step_expr
|
|
126
|
+
start = self._eval_with_vars(start_expr, run_vars)
|
|
127
|
+
end = self._eval_with_vars(end_expr, run_vars)
|
|
128
|
+
step = self._eval_with_vars(step_expr, run_vars) if step_expr else 1
|
|
129
|
+
try:
|
|
130
|
+
if start == int(start): start = int(start)
|
|
131
|
+
except (OverflowError, ValueError):
|
|
132
|
+
pass
|
|
133
|
+
try:
|
|
134
|
+
if end == int(end): end = int(end)
|
|
135
|
+
except (OverflowError, ValueError):
|
|
136
|
+
pass
|
|
137
|
+
try:
|
|
138
|
+
if isinstance(step, float) and step == int(step): step = int(step)
|
|
139
|
+
except (OverflowError, ValueError):
|
|
140
|
+
pass
|
|
141
|
+
run_vars[var] = start
|
|
142
|
+
self.variables[var] = start
|
|
143
|
+
loop_stack.append({'var': var, 'current': start, 'end': end,
|
|
144
|
+
'step': step, 'return_ip': ip})
|
|
145
|
+
return True, ExecResult.ADVANCE
|
|
146
|
+
|
|
147
|
+
def _cf_next(self, stmt: str, run_vars: dict[str, Any], loop_stack: list[dict[str, Any]],
|
|
148
|
+
parsed: NextStmt) -> tuple[bool, ExecOutcome]:
|
|
149
|
+
var = parsed.var
|
|
150
|
+
if not loop_stack or loop_stack[-1].get('var') != var:
|
|
151
|
+
if loop_stack:
|
|
152
|
+
expected = loop_stack[-1].get('var', '?')
|
|
153
|
+
raise RuntimeError(f"NEXT {var} does not match current FOR {expected}")
|
|
154
|
+
raise RuntimeError(f"NEXT {var} without matching FOR")
|
|
155
|
+
loop = loop_stack[-1]
|
|
156
|
+
loop['current'] += loop['step']
|
|
157
|
+
if (loop['step'] > 0 and loop['current'] <= loop['end']) or \
|
|
158
|
+
(loop['step'] < 0 and loop['current'] >= loop['end']):
|
|
159
|
+
run_vars[var] = loop['current']
|
|
160
|
+
self.variables[var] = loop['current']
|
|
161
|
+
return True, loop['return_ip'] + 1
|
|
162
|
+
else:
|
|
163
|
+
loop_stack.pop()
|
|
164
|
+
return True, ExecResult.ADVANCE
|
|
165
|
+
|
|
166
|
+
def _find_matching_wend(self, sorted_lines: list[int], ip: int) -> int:
|
|
167
|
+
"""Find the ip after the WEND matching the WHILE at ip.
|
|
168
|
+
|
|
169
|
+
Scans forward with proper nesting depth tracking. Returns the ip
|
|
170
|
+
index past the matching WEND. Raises with the WHILE line number
|
|
171
|
+
for clear diagnostics.
|
|
172
|
+
"""
|
|
173
|
+
depth = 1
|
|
174
|
+
scan = ip + 1
|
|
175
|
+
while scan < len(sorted_lines):
|
|
176
|
+
s = self.program[sorted_lines[scan]].strip().upper()
|
|
177
|
+
if s.startswith('WHILE '):
|
|
178
|
+
depth += 1
|
|
179
|
+
elif s == 'WEND':
|
|
180
|
+
depth -= 1
|
|
181
|
+
if depth == 0:
|
|
182
|
+
return scan + 1
|
|
183
|
+
scan += 1
|
|
184
|
+
line_num = sorted_lines[ip]
|
|
185
|
+
raise RuntimeError(f"WHILE at line {line_num} has no matching WEND")
|
|
186
|
+
|
|
187
|
+
def _cf_while(self, stmt: str, run_vars: dict[str, Any], loop_stack: list[dict[str, Any]],
|
|
188
|
+
sorted_lines: list[int], ip: int,
|
|
189
|
+
parsed: WhileStmt) -> tuple[bool, ExecOutcome]:
|
|
190
|
+
cond = parsed.condition
|
|
191
|
+
if self._eval_condition(cond, run_vars):
|
|
192
|
+
loop_stack.append({'type': 'while', 'cond': cond, 'return_ip': ip})
|
|
193
|
+
return True, ExecResult.ADVANCE
|
|
194
|
+
else:
|
|
195
|
+
return True, self._find_matching_wend(sorted_lines, ip)
|
|
196
|
+
|
|
197
|
+
def _cf_wend(self, run_vars: dict[str, Any], loop_stack: list[dict[str, Any]],
|
|
198
|
+
sorted_lines: list[int] | None = None, ip: int | None = None) -> tuple[bool, ExecOutcome] | None:
|
|
199
|
+
if not loop_stack or loop_stack[-1].get('type') != 'while':
|
|
200
|
+
ctx = f" at line {sorted_lines[ip]}" if sorted_lines and ip is not None else ""
|
|
201
|
+
raise RuntimeError(f"WEND{ctx} without matching WHILE")
|
|
202
|
+
loop = loop_stack[-1]
|
|
203
|
+
if self._eval_condition(loop['cond'], run_vars):
|
|
204
|
+
return True, loop['return_ip']
|
|
205
|
+
else:
|
|
206
|
+
loop_stack.pop()
|
|
207
|
+
return True, ExecResult.ADVANCE
|
|
208
|
+
|
|
209
|
+
def _cf_if_then(self, stmt: str, run_vars: dict[str, Any], loop_stack: list[dict[str, Any]],
|
|
210
|
+
sorted_lines: list[int], ip: int,
|
|
211
|
+
exec_fn: Callable[..., Any],
|
|
212
|
+
parsed: IfThenStmt) -> tuple[bool, ExecOutcome]:
|
|
213
|
+
cond_str = parsed.condition
|
|
214
|
+
then_clause = parsed.then_clause
|
|
215
|
+
else_clause = parsed.else_clause
|
|
216
|
+
cond_vars = run_vars
|
|
217
|
+
if self.locc_mode and self.locc:
|
|
218
|
+
cond_vars = {**run_vars, **self.locc.classical}
|
|
219
|
+
result = ExecResult.ADVANCE
|
|
220
|
+
if self._eval_condition(cond_str, cond_vars):
|
|
221
|
+
if then_clause:
|
|
222
|
+
r = exec_fn(then_clause, loop_stack, sorted_lines, ip, run_vars)
|
|
223
|
+
if r is not None and r is not ExecResult.ADVANCE:
|
|
224
|
+
result = r
|
|
225
|
+
elif else_clause:
|
|
226
|
+
r = exec_fn(else_clause, loop_stack, sorted_lines, ip, run_vars)
|
|
227
|
+
if r is not None and r is not ExecResult.ADVANCE:
|
|
228
|
+
result = r
|
|
229
|
+
return True, result
|
|
230
|
+
|
|
231
|
+
# ── Type-based dispatch table ────────────────────────────────────
|
|
232
|
+
# Maps parsed Stmt types to handler lambdas. Each lambda receives
|
|
233
|
+
# (self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars,
|
|
234
|
+
# exec_fn) and returns (handled: bool, result).
|
|
235
|
+
|
|
236
|
+
_CF_DISPATCH: dict[type, Callable] = {
|
|
237
|
+
# Trivial handlers (no _cf_* method needed)
|
|
238
|
+
RemStmt: lambda s, st, p, ls, sl, ip, rv, ef: (True, ExecResult.ADVANCE),
|
|
239
|
+
MeasureStmt: lambda s, st, p, ls, sl, ip, rv, ef: (True, ExecResult.ADVANCE),
|
|
240
|
+
EndStmt: lambda s, st, p, ls, sl, ip, rv, ef: (True, ExecResult.END),
|
|
241
|
+
ReturnStmt: lambda s, st, p, ls, sl, ip, rv, ef: (_ for _ in ()).throw(RuntimeError("RETURN WITHOUT GOSUB")) if not s._gosub_stack else (True, s._gosub_stack.pop()),
|
|
242
|
+
# Handlers defined in control_flow.py (parsed is positional)
|
|
243
|
+
WendStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_wend(rv, ls, sl, ip),
|
|
244
|
+
LetArrayStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_let_array(st, rv, p),
|
|
245
|
+
LetStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_let_var(st, rv, p),
|
|
246
|
+
PrintStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_print(st, rv, p),
|
|
247
|
+
GotoStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_goto(st, sl, p),
|
|
248
|
+
GosubStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_gosub(st, sl, ip, p),
|
|
249
|
+
ForStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_for(st, rv, ls, ip, p),
|
|
250
|
+
NextStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_next(st, rv, ls, p),
|
|
251
|
+
WhileStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_while(st, rv, ls, sl, ip, p),
|
|
252
|
+
IfThenStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_if_then(st, rv, ls, sl, ip, ef, p),
|
|
253
|
+
# Handlers defined in classic.py (parsed is keyword-only)
|
|
254
|
+
DataStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_data(st, parsed=p),
|
|
255
|
+
ReadStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_read(st, rv, parsed=p),
|
|
256
|
+
OnGotoStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_on_goto(st, rv, sl, parsed=p),
|
|
257
|
+
OnGosubStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_on_gosub(st, rv, sl, ip, parsed=p),
|
|
258
|
+
SelectCaseStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_select_case(st, rv, sl, ip, parsed=p),
|
|
259
|
+
CaseStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_case(st, sl, ip, parsed=p),
|
|
260
|
+
EndSelectStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_end_select(st, parsed=p),
|
|
261
|
+
DoStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_do(st, rv, ls, sl, ip, parsed=p),
|
|
262
|
+
LoopStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_loop(st, rv, ls, sl, ip, parsed=p),
|
|
263
|
+
ExitStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_exit(st, ls, sl, ip, parsed=p),
|
|
264
|
+
SwapStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_swap(st, rv, parsed=p),
|
|
265
|
+
DefFnStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_def_fn(st, rv, parsed=p),
|
|
266
|
+
OptionBaseStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_option_base(st, parsed=p),
|
|
267
|
+
# Handlers defined in subs.py (parsed is keyword-only)
|
|
268
|
+
SubStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_sub(st, sl, ip, parsed=p),
|
|
269
|
+
EndSubStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_end_sub(st, parsed=p),
|
|
270
|
+
FunctionStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_function(st, sl, ip, parsed=p),
|
|
271
|
+
EndFunctionStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_end_function(st, parsed=p),
|
|
272
|
+
CallStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_call(st, rv, sl, ip, parsed=p),
|
|
273
|
+
LocalStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_local(st, rv, parsed=p),
|
|
274
|
+
StaticStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_static(st, rv, parsed=p),
|
|
275
|
+
SharedStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_shared(st, rv, parsed=p),
|
|
276
|
+
# Handlers defined in debug.py (parsed is keyword-only)
|
|
277
|
+
OnErrorStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_on_error(st, parsed=p),
|
|
278
|
+
ResumeStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_resume(st, sl, parsed=p),
|
|
279
|
+
ErrorStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_error(st, parsed=p),
|
|
280
|
+
AssertStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_assert(st, rv, parsed=p),
|
|
281
|
+
StopStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_stop(st, sl, ip, parsed=p),
|
|
282
|
+
OnMeasureStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_on_measure(st, parsed=p),
|
|
283
|
+
OnTimerStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_on_timer(st, parsed=p),
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
def _exec_control_flow(
|
|
287
|
+
self, stmt: str, loop_stack: list[dict[str, Any]],
|
|
288
|
+
sorted_lines: list[int], ip: int, run_vars: dict[str, Any],
|
|
289
|
+
exec_fn: Callable[..., Any],
|
|
290
|
+
*, parsed=None,
|
|
291
|
+
) -> tuple[bool, ExecOutcome | None]:
|
|
292
|
+
"""Shared control flow for both Qiskit and LOCC execution paths.
|
|
293
|
+
Returns (handled, result) -- if handled is True, result is the
|
|
294
|
+
return value. exec_fn is the recursive line executor for
|
|
295
|
+
IF/multi-statement dispatch.
|
|
296
|
+
|
|
297
|
+
Dispatches via dict lookup on the parsed Stmt type (O(1)).
|
|
298
|
+
|
|
299
|
+
If *parsed* is provided, the parse_stmt call is skipped (avoids
|
|
300
|
+
redundant parsing when the caller has already parsed the statement).
|
|
301
|
+
"""
|
|
302
|
+
if parsed is None:
|
|
303
|
+
parsed = parse_stmt(stmt)
|
|
304
|
+
handler = self._CF_DISPATCH.get(type(parsed))
|
|
305
|
+
if handler is not None:
|
|
306
|
+
return handler(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn)
|
|
307
|
+
|
|
308
|
+
# RawStmt or unmapped type -- not handled by control flow
|
|
309
|
+
return False, None
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import time
|
|
6
6
|
|
|
7
|
-
MAX_SV_CHECKPOINTS =
|
|
7
|
+
MAX_SV_CHECKPOINTS = 200
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
10
|
from qubasic_core.engine import (
|
|
@@ -131,8 +131,14 @@ class DebugMixin:
|
|
|
131
131
|
# ── Time-travel debugging ─────────────────────────────────────────
|
|
132
132
|
|
|
133
133
|
def _checkpoint_sv(self, line_num: int) -> None:
|
|
134
|
-
"""Save a statevector checkpoint (for small qubit counts).
|
|
135
|
-
|
|
134
|
+
"""Save a statevector checkpoint (for small qubit counts).
|
|
135
|
+
|
|
136
|
+
Only checkpoints systems with <= 12 qubits to keep memory reasonable.
|
|
137
|
+
12 qubits = 2^12 complex128 = ~64KB per checkpoint.
|
|
138
|
+
At MAX_SV_CHECKPOINTS=200 that is ~12MB total, versus ~1GB for 16-qubit
|
|
139
|
+
systems at the old limit.
|
|
140
|
+
"""
|
|
141
|
+
if self.last_sv is not None and self.num_qubits <= 12:
|
|
136
142
|
import numpy as np
|
|
137
143
|
self._sv_checkpoints.append((line_num, np.array(self.last_sv).copy()))
|
|
138
144
|
if len(self._sv_checkpoints) > MAX_SV_CHECKPOINTS:
|
|
@@ -225,7 +231,7 @@ class DebugMixin:
|
|
|
225
231
|
# ── Breakpoints ────────────────────────────────────────────────────
|
|
226
232
|
|
|
227
233
|
def cmd_breakpoint(self, rest: str) -> None:
|
|
228
|
-
"""BREAK <line> —
|
|
234
|
+
"""BREAK <line> | BREAK CLEAR | BREAK LIST | BREAK <start>-<end> — manage breakpoints."""
|
|
229
235
|
if not rest.strip():
|
|
230
236
|
if self._breakpoints:
|
|
231
237
|
self.io.writeln(f" Breakpoints: {sorted(self._breakpoints)}")
|
|
@@ -233,10 +239,29 @@ class DebugMixin:
|
|
|
233
239
|
self.io.writeln(" No breakpoints set")
|
|
234
240
|
return
|
|
235
241
|
rest = rest.strip().upper()
|
|
236
|
-
if rest
|
|
242
|
+
if rest in ('CLEAR', 'ALL'):
|
|
237
243
|
self._breakpoints.clear()
|
|
238
244
|
self.io.writeln("BREAKPOINTS CLEARED")
|
|
239
245
|
return
|
|
246
|
+
if rest == 'LIST':
|
|
247
|
+
if self._breakpoints:
|
|
248
|
+
for bp in sorted(self._breakpoints):
|
|
249
|
+
src = self.program.get(bp, '(no source)')
|
|
250
|
+
self.io.writeln(f" {bp}: {src}")
|
|
251
|
+
else:
|
|
252
|
+
self.io.writeln(" No breakpoints set")
|
|
253
|
+
return
|
|
254
|
+
# Range: BREAK 10-50
|
|
255
|
+
if '-' in rest:
|
|
256
|
+
try:
|
|
257
|
+
a, b = rest.split('-')
|
|
258
|
+
for bp in list(self._breakpoints):
|
|
259
|
+
if int(a) <= bp <= int(b):
|
|
260
|
+
self._breakpoints.discard(bp)
|
|
261
|
+
self.io.writeln(f"BREAKPOINTS CLEARED IN {a}-{b}")
|
|
262
|
+
return
|
|
263
|
+
except ValueError:
|
|
264
|
+
pass
|
|
240
265
|
try:
|
|
241
266
|
line = int(rest)
|
|
242
267
|
if line in self._breakpoints:
|
|
@@ -246,7 +271,7 @@ class DebugMixin:
|
|
|
246
271
|
self._breakpoints.add(line)
|
|
247
272
|
self.io.writeln(f"BREAKPOINT SET: {line}")
|
|
248
273
|
except ValueError:
|
|
249
|
-
self.io.writeln("?USAGE: BREAK <line> | BREAK CLEAR")
|
|
274
|
+
self.io.writeln("?USAGE: BREAK <line> | BREAK <start>-<end> | BREAK CLEAR | BREAK LIST")
|
|
250
275
|
|
|
251
276
|
def _check_breakpoint(self, line_num: int, sorted_lines: list[int], ip: int) -> bool:
|
|
252
277
|
"""Check if we should break at this line. Returns True to stop."""
|
|
@@ -248,10 +248,12 @@ class DisplayMixin:
|
|
|
248
248
|
|
|
249
249
|
# Labels on the sphere
|
|
250
250
|
self.io.writeln(f" Qubit {qubit} ({x:.3f}, {y:.3f}, {z:.3f}) {label}")
|
|
251
|
-
|
|
251
|
+
_ket0 = '|0\u27E9'
|
|
252
|
+
_ket1 = '|1\u27E9'
|
|
253
|
+
self.io.writeln(f"{_ket0:^{W+4}}")
|
|
252
254
|
for row in grid:
|
|
253
255
|
self.io.writeln(f" {''.join(row)}")
|
|
254
|
-
self.io.writeln(f"{
|
|
256
|
+
self.io.writeln(f"{_ket1:^{W+4}}")
|
|
255
257
|
|
|
256
258
|
def _bloch_vector(self, sv, qubit, n_qubits=None):
|
|
257
259
|
"""Compute the Bloch vector for a single qubit from the statevector."""
|