qubasic 0.4.0__tar.gz → 0.4.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.4.0 → qubasic-0.4.1}/CHANGELOG.md +17 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/LICENSE +1 -1
- {qubasic-0.4.0/qubasic.egg-info → qubasic-0.4.1}/PKG-INFO +1 -1
- {qubasic-0.4.0 → qubasic-0.4.1}/pyproject.toml +1 -1
- {qubasic-0.4.0 → qubasic-0.4.1/qubasic.egg-info}/PKG-INFO +1 -1
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic.py +17 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/__init__.py +1 -1
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/classic.py +3 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/control_flow.py +5 -3
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/demos.py +1 -1
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/expression.py +9 -25
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/locc_commands.py +9 -5
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/locc_display.py +3 -3
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/locc_execution.py +5 -5
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/parser.py +2 -2
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/profiler.py +6 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/scope.py +0 -1
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/subs.py +25 -1
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/terminal.py +2 -2
- {qubasic-0.4.0 → qubasic-0.4.1}/MANIFEST.in +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/README.md +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/examples/bell.qb +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/examples/grover3.qb +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/examples/locc_teleport.qb +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/examples/sweep_rx.qb +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic.egg-info/SOURCES.txt +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic.egg-info/dependency_links.txt +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic.egg-info/entry_points.txt +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic.egg-info/requires.txt +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic.egg-info/top_level.txt +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/__main__.py +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/analysis.py +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/backend.py +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/debug.py +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/display.py +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/engine.py +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/engine_state.py +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/errors.py +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/exec_context.py +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/executor.py +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/file_io.py +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/gates.py +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/help_text.py +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/io_protocol.py +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/locc.py +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/locc_engine.py +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/memory.py +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/mock_backend.py +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/noise_mixin.py +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/patterns.py +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/program_mgmt.py +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/protocol.py +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/screen.py +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/state_display.py +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/statements.py +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/strings.py +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/qubasic_core/sweep.py +0 -0
- {qubasic-0.4.0 → qubasic-0.4.1}/setup.cfg +0 -0
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.1 (2026-03-30)
|
|
4
|
+
|
|
5
|
+
- **Fix SEED dispatch**: moved from no-arg to with-arg dispatch table so `SEED 42` works from the REPL
|
|
6
|
+
- **Fix GPU probe**: `cmd_method` GPU probe used undefined `_pqc` variable; now uses `_pqc_m`
|
|
7
|
+
- **Fix LOCC non-numeric args**: `LOCC 4 banana` no longer crashes with unguarded ValueError
|
|
8
|
+
- **Coverage threshold**: raised CI coverage floor from 60% to 75%
|
|
9
|
+
- **Property-based tests**: 4 new hypothesis tests (arithmetic identity, parser fuzzing, process fuzzing, FOR loop count)
|
|
10
|
+
- **CLI integration tests**: 8 new tests covering dispatch, SEED, LOCC error handling, METHOD probe, --seed flag, --help
|
|
11
|
+
- **Parser imports**: import regexes from patterns.py directly instead of double-indirection through engine.py
|
|
12
|
+
- **Expression simplification**: `_replace_dollar_outside_strings` reduced from two passes to one
|
|
13
|
+
- **Jump table**: pre-compute WHILE/WEND and DO/LOOP ip mappings in `_scan_subs`; `_find_matching_wend` and `_find_matching_loop` use O(1) lookup when available
|
|
14
|
+
- **Scope cleanup**: removed dual-write hack in `Scope.__setitem__`; writes go to `_runtime` only, `_persistent` is read-through fallback
|
|
15
|
+
- **STATS output**: redirect stdout during stats runs to suppress rich console output in non-TTY mode
|
|
16
|
+
- **Copyright year**: LICENSE updated from 2025 to 2026
|
|
17
|
+
- **CLI --seed flag**: `qubasic --seed N script.qb` sets deterministic seed before execution
|
|
18
|
+
- **Type annotations**: added return type annotations to 14 unannotated mixin methods across locc_commands, locc_display, locc_execution, demos
|
|
19
|
+
|
|
3
20
|
## 0.4.0 (2026-03-29)
|
|
4
21
|
|
|
5
22
|
- **Noise correctness**: transpile with optimization_level=0 when noisy so gates survive for noise attachment
|
|
@@ -53,10 +53,22 @@ def main():
|
|
|
53
53
|
args = sys.argv[1:]
|
|
54
54
|
quiet = '--quiet' in args or '-q' in args
|
|
55
55
|
json_mode = '--json' in args
|
|
56
|
+
seed_val = None
|
|
56
57
|
if quiet:
|
|
57
58
|
args = [a for a in args if a not in ('--quiet', '-q')]
|
|
58
59
|
if json_mode:
|
|
59
60
|
args = [a for a in args if a != '--json']
|
|
61
|
+
# Parse --seed N
|
|
62
|
+
filtered = []
|
|
63
|
+
i = 0
|
|
64
|
+
while i < len(args):
|
|
65
|
+
if args[i] == '--seed' and i + 1 < len(args):
|
|
66
|
+
seed_val = int(args[i + 1])
|
|
67
|
+
i += 2
|
|
68
|
+
else:
|
|
69
|
+
filtered.append(args[i])
|
|
70
|
+
i += 1
|
|
71
|
+
args = filtered
|
|
60
72
|
|
|
61
73
|
if any(a in ('-h', '--help') for a in args):
|
|
62
74
|
from qubasic_core import __version__
|
|
@@ -67,12 +79,17 @@ def main():
|
|
|
67
79
|
print(" qubasic script.qb Run a script file")
|
|
68
80
|
print(" qubasic --quiet script Suppress banner and progress")
|
|
69
81
|
print(" qubasic --json script Output results as JSON")
|
|
82
|
+
print(" qubasic --seed N script Set random seed for reproducibility")
|
|
70
83
|
print(" qubasic --help Show this help")
|
|
71
84
|
print()
|
|
72
85
|
print("Type HELP inside the REPL for full command reference.")
|
|
73
86
|
sys.exit(0)
|
|
74
87
|
|
|
75
88
|
term = QBasicTerminal()
|
|
89
|
+
if seed_val is not None:
|
|
90
|
+
import numpy as _np
|
|
91
|
+
term._seed = seed_val
|
|
92
|
+
_np.random.seed(seed_val)
|
|
76
93
|
|
|
77
94
|
if args:
|
|
78
95
|
path = args[0]
|
|
@@ -217,6 +217,9 @@ class ClassicMixin:
|
|
|
217
217
|
# ── DO / LOOP ─────────────────────────────────────────────────────
|
|
218
218
|
|
|
219
219
|
def _find_matching_loop(self, sorted_lines: list[int], ip: int) -> int:
|
|
220
|
+
jt = getattr(self, '_jump_table', None)
|
|
221
|
+
if jt and ip in jt:
|
|
222
|
+
return jt[ip]
|
|
220
223
|
depth = 1
|
|
221
224
|
scan = ip + 1
|
|
222
225
|
while scan < len(sorted_lines):
|
|
@@ -166,10 +166,12 @@ class ControlFlowMixin:
|
|
|
166
166
|
def _find_matching_wend(self, sorted_lines: list[int], ip: int) -> int:
|
|
167
167
|
"""Find the ip after the WEND matching the WHILE at ip.
|
|
168
168
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
for clear diagnostics.
|
|
169
|
+
Uses pre-computed jump table when available (O(1)), falls back
|
|
170
|
+
to linear scan with nesting depth tracking.
|
|
172
171
|
"""
|
|
172
|
+
jt = getattr(self, '_jump_table', None)
|
|
173
|
+
if jt and ip in jt:
|
|
174
|
+
return jt[ip] + 1
|
|
173
175
|
depth = 1
|
|
174
176
|
scan = ip + 1
|
|
175
177
|
while scan < len(sorted_lines):
|
|
@@ -12,44 +12,28 @@ from typing import Any
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def _replace_dollar_outside_strings(expr_str: str) -> str:
|
|
15
|
-
"""Apply $->_S_ replacement only outside quoted string literals.
|
|
16
|
-
|
|
15
|
+
"""Apply $->_S_ replacement only outside quoted string literals.
|
|
16
|
+
|
|
17
|
+
Single pass: splits on quote boundaries, substitutes only in
|
|
18
|
+
unquoted segments.
|
|
19
|
+
"""
|
|
20
|
+
parts: list[str] = []
|
|
17
21
|
i = 0
|
|
18
22
|
n = len(expr_str)
|
|
19
23
|
while i < n:
|
|
20
24
|
ch = expr_str[i]
|
|
21
25
|
if ch in ('"', "'"):
|
|
22
|
-
# Copy the entire quoted region verbatim
|
|
23
26
|
quote = ch
|
|
24
27
|
j = i + 1
|
|
25
28
|
while j < n and expr_str[j] != quote:
|
|
26
29
|
j += 1
|
|
27
|
-
|
|
28
|
-
result.append(expr_str[i:j + 1])
|
|
29
|
-
i = j + 1
|
|
30
|
-
else:
|
|
31
|
-
result.append(ch)
|
|
32
|
-
i += 1
|
|
33
|
-
text = ''.join(result)
|
|
34
|
-
# Now apply the substitution only to non-quoted segments
|
|
35
|
-
parts: list[str] = []
|
|
36
|
-
i = 0
|
|
37
|
-
n = len(text)
|
|
38
|
-
while i < n:
|
|
39
|
-
ch = text[i]
|
|
40
|
-
if ch in ('"', "'"):
|
|
41
|
-
quote = ch
|
|
42
|
-
j = i + 1
|
|
43
|
-
while j < n and text[j] != quote:
|
|
44
|
-
j += 1
|
|
45
|
-
parts.append(text[i:j + 1])
|
|
30
|
+
parts.append(expr_str[i:j + 1])
|
|
46
31
|
i = j + 1
|
|
47
32
|
else:
|
|
48
|
-
# Accumulate non-quoted text
|
|
49
33
|
j = i
|
|
50
|
-
while j < n and
|
|
34
|
+
while j < n and expr_str[j] not in ('"', "'"):
|
|
51
35
|
j += 1
|
|
52
|
-
segment =
|
|
36
|
+
segment = expr_str[i:j]
|
|
53
37
|
parts.append(re.sub(r'(\w+)\$', r'\1_S_', segment))
|
|
54
38
|
i = j
|
|
55
39
|
return ''.join(parts)
|
|
@@ -24,7 +24,7 @@ class LOCCCommandsMixin:
|
|
|
24
24
|
"""
|
|
25
25
|
return getattr(self, '_noise_depol_p', 0.0)
|
|
26
26
|
|
|
27
|
-
def cmd_locc(self, rest):
|
|
27
|
+
def cmd_locc(self, rest: str) -> None:
|
|
28
28
|
args = rest.upper().split()
|
|
29
29
|
if not args:
|
|
30
30
|
if self.locc_mode:
|
|
@@ -86,7 +86,11 @@ class LOCCCommandsMixin:
|
|
|
86
86
|
self.io.writeln(f"?MAX {LOCC_MAX_REGISTERS} registers (A-Z)")
|
|
87
87
|
return
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
try:
|
|
90
|
+
sizes = [int(n) for n in nums]
|
|
91
|
+
except ValueError:
|
|
92
|
+
self.io.writeln("?LOCC register sizes must be integers")
|
|
93
|
+
return
|
|
90
94
|
total = sum(sizes)
|
|
91
95
|
if joint and total > LOCC_MAX_JOINT_QUBITS:
|
|
92
96
|
self.io.writeln(f"?JOINT mode limited to {LOCC_MAX_JOINT_QUBITS} total qubits (requested {total})")
|
|
@@ -130,7 +134,7 @@ class LOCCCommandsMixin:
|
|
|
130
134
|
self.io.writeln(f" WARNING: non-depolarizing noise model active but not supported "
|
|
131
135
|
f"in LOCC numpy path. Only NOISE depolarizing propagates to LOCC.")
|
|
132
136
|
|
|
133
|
-
def cmd_send(self, rest):
|
|
137
|
+
def cmd_send(self, rest: str) -> None:
|
|
134
138
|
if not self.locc_mode:
|
|
135
139
|
self.io.writeln("?SEND requires LOCC mode")
|
|
136
140
|
return
|
|
@@ -153,7 +157,7 @@ class LOCCCommandsMixin:
|
|
|
153
157
|
self.locc.classical[var] = outcome
|
|
154
158
|
self.io.writeln(f" {reg}[{qubit}] -> {var} = {outcome}")
|
|
155
159
|
|
|
156
|
-
def cmd_share(self, rest):
|
|
160
|
+
def cmd_share(self, rest: str) -> None:
|
|
157
161
|
if not self.locc_mode:
|
|
158
162
|
self.io.writeln("?SHARE requires LOCC mode")
|
|
159
163
|
return
|
|
@@ -173,7 +177,7 @@ class LOCCCommandsMixin:
|
|
|
173
177
|
except RuntimeError as e:
|
|
174
178
|
self.io.writeln(f"?{e}")
|
|
175
179
|
|
|
176
|
-
def cmd_loccinfo(self):
|
|
180
|
+
def cmd_loccinfo(self) -> None:
|
|
177
181
|
"""Show LOCC protocol metrics after a run."""
|
|
178
182
|
if not self.locc_mode:
|
|
179
183
|
self.io.writeln("?NOT IN LOCC MODE")
|
|
@@ -10,7 +10,7 @@ class LOCCDisplayMixin:
|
|
|
10
10
|
self._print_bloch_single(), self.print_histogram(), self.io.
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
-
def _locc_state(self, rest=''):
|
|
13
|
+
def _locc_state(self, rest: str = '') -> None:
|
|
14
14
|
reg = rest.strip().upper() if rest else ''
|
|
15
15
|
if self.locc.joint:
|
|
16
16
|
if not reg or reg in self.locc.names:
|
|
@@ -24,7 +24,7 @@ class LOCCDisplayMixin:
|
|
|
24
24
|
self.io.writeln(f"\n Register {name} ({size} qubits):")
|
|
25
25
|
self._print_statevector(self.locc.svs[name], size)
|
|
26
26
|
|
|
27
|
-
def _locc_bloch(self, rest):
|
|
27
|
+
def _locc_bloch(self, rest: str) -> None:
|
|
28
28
|
m = re.match(r'([A-Z])\s*(\d*)', rest.strip(), re.IGNORECASE) if rest.strip() else None
|
|
29
29
|
if m and m.group(1):
|
|
30
30
|
reg = m.group(1).upper()
|
|
@@ -49,7 +49,7 @@ class LOCCDisplayMixin:
|
|
|
49
49
|
else:
|
|
50
50
|
self.io.writeln(f"?USAGE: BLOCH <reg> [qubit] (registers: {', '.join(self.locc.names)})")
|
|
51
51
|
|
|
52
|
-
def _locc_display_results(self, per_reg, counts_joint):
|
|
52
|
+
def _locc_display_results(self, per_reg: dict, counts_joint: dict) -> None:
|
|
53
53
|
"""Display per-register and joint histograms."""
|
|
54
54
|
for name in self.locc.names:
|
|
55
55
|
size = self.locc.get_size(name)
|
|
@@ -27,7 +27,7 @@ class LOCCExecutionMixin:
|
|
|
27
27
|
self.last_counts, self.io.
|
|
28
28
|
"""
|
|
29
29
|
|
|
30
|
-
def _locc_run(self):
|
|
30
|
+
def _locc_run(self) -> None:
|
|
31
31
|
"""Execute program in LOCC mode (N registers)."""
|
|
32
32
|
sorted_lines = sorted(self.program.keys())
|
|
33
33
|
if not sorted_lines:
|
|
@@ -44,7 +44,7 @@ class LOCCExecutionMixin:
|
|
|
44
44
|
# Sync last_sv from LOCC engine so EXPECT/DENSITY/BLOCH work
|
|
45
45
|
self.last_sv = self._active_sv
|
|
46
46
|
|
|
47
|
-
def _locc_run_with_send(self, sorted_lines, has_measure):
|
|
47
|
+
def _locc_run_with_send(self, sorted_lines: list[int], has_measure: bool) -> None:
|
|
48
48
|
"""LOCC execution with SEND — prefix/suffix split optimization.
|
|
49
49
|
|
|
50
50
|
Executes the deterministic prefix (before first SEND) once,
|
|
@@ -148,7 +148,7 @@ class LOCCExecutionMixin:
|
|
|
148
148
|
self._locc_display_results(per_reg, counts_joint)
|
|
149
149
|
self.last_counts = counts_joint
|
|
150
150
|
|
|
151
|
-
def _locc_run_vectorized(self, sorted_lines, has_measure):
|
|
151
|
+
def _locc_run_vectorized(self, sorted_lines: list[int], has_measure: bool) -> None:
|
|
152
152
|
"""LOCC execution without SEND — single execution, vectorized sampling.
|
|
153
153
|
|
|
154
154
|
When noise is active, re-executes per shot so that Monte Carlo noise
|
|
@@ -314,7 +314,7 @@ class LOCCExecutionMixin:
|
|
|
314
314
|
|
|
315
315
|
raise ValueError(f"LOCC mode requires @A/@B prefix, SEND, SHARE, or IF: {stmt}")
|
|
316
316
|
|
|
317
|
-
def _locc_try_special(self, reg, stmt, run_vars):
|
|
317
|
+
def _locc_try_special(self, reg: str, stmt: str, run_vars: dict) -> bool:
|
|
318
318
|
"""Handle MEAS/RESET/MEASURE_X/Y/Z/SYNDROME in LOCC mode."""
|
|
319
319
|
from qubasic_core.parser import parse_stmt
|
|
320
320
|
from qubasic_core.statements import MeasStmt, ResetStmt, MeasureBasisStmt, SyndromeStmt
|
|
@@ -373,7 +373,7 @@ class LOCCExecutionMixin:
|
|
|
373
373
|
|
|
374
374
|
return False
|
|
375
375
|
|
|
376
|
-
def _locc_apply_gate(self, reg, gate_stmt):
|
|
376
|
+
def _locc_apply_gate(self, reg: str, gate_stmt: str) -> None:
|
|
377
377
|
"""Parse and apply a gate to a LOCC register via numpy.
|
|
378
378
|
|
|
379
379
|
Uses LOCCRegBackend for standard gates. CTRL and INV modifiers
|
|
@@ -4,8 +4,8 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import re
|
|
6
6
|
|
|
7
|
-
from qubasic_core.
|
|
8
|
-
|
|
7
|
+
from qubasic_core.gates import GATE_TABLE, GATE_ALIASES
|
|
8
|
+
from qubasic_core.patterns import (
|
|
9
9
|
RE_LET_ARRAY, RE_LET_VAR, RE_PRINT,
|
|
10
10
|
RE_GOTO, RE_GOSUB, RE_FOR, RE_NEXT, RE_WHILE, RE_IF_THEN,
|
|
11
11
|
RE_MEAS, RE_RESET, RE_SEND, RE_SHARE, RE_AT_REG_LINE, RE_AT_REG,
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import math
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
6
8
|
import time
|
|
7
9
|
from typing import Any
|
|
8
10
|
|
|
@@ -111,10 +113,14 @@ class ProfilerMixin:
|
|
|
111
113
|
self.io.writeln(f"\nRunning {n} trials...")
|
|
112
114
|
for trial in range(n):
|
|
113
115
|
old_io = self.io
|
|
116
|
+
old_stdout = sys.stdout
|
|
114
117
|
self.io = _NullIOPort()
|
|
118
|
+
sys.stdout = open(os.devnull, 'w')
|
|
115
119
|
try:
|
|
116
120
|
self.cmd_run()
|
|
117
121
|
finally:
|
|
122
|
+
sys.stdout.close()
|
|
123
|
+
sys.stdout = old_stdout
|
|
118
124
|
self.io = old_io
|
|
119
125
|
if self.last_counts:
|
|
120
126
|
if len(self._stats_runs) >= MAX_STATS_RUNS:
|
|
@@ -29,9 +29,12 @@ class SubroutineMixin:
|
|
|
29
29
|
self._func_call_depth: int = 0
|
|
30
30
|
|
|
31
31
|
def _scan_subs(self, sorted_lines: list[int]) -> None:
|
|
32
|
-
"""Scan program for SUB and
|
|
32
|
+
"""Scan program for SUB/FUNCTION blocks and build jump table."""
|
|
33
33
|
self._sub_defs.clear()
|
|
34
34
|
self._func_defs.clear()
|
|
35
|
+
self._jump_table: dict[int, int] = {} # ip -> matching-end ip
|
|
36
|
+
# Build jump table for WHILE/WEND, DO/LOOP pairs
|
|
37
|
+
self._build_jump_table(sorted_lines)
|
|
35
38
|
for i, ln in enumerate(sorted_lines):
|
|
36
39
|
stmt = self.program[ln].strip()
|
|
37
40
|
m = RE_SUB.match(stmt)
|
|
@@ -48,6 +51,27 @@ class SubroutineMixin:
|
|
|
48
51
|
end_ip = self._find_end_block(sorted_lines, i, 'FUNCTION')
|
|
49
52
|
self._func_defs[name] = {'params': params, 'start_ip': i + 1, 'end_ip': end_ip}
|
|
50
53
|
|
|
54
|
+
def _build_jump_table(self, sorted_lines: list[int]) -> None:
|
|
55
|
+
"""Pre-compute WHILE->WEND and DO->LOOP ip mappings."""
|
|
56
|
+
while_stack: list[int] = []
|
|
57
|
+
do_stack: list[int] = []
|
|
58
|
+
for ip, ln in enumerate(sorted_lines):
|
|
59
|
+
s = self.program[ln].strip().upper()
|
|
60
|
+
if s.startswith('WHILE '):
|
|
61
|
+
while_stack.append(ip)
|
|
62
|
+
elif s == 'WEND':
|
|
63
|
+
if while_stack:
|
|
64
|
+
w_ip = while_stack.pop()
|
|
65
|
+
self._jump_table[w_ip] = ip
|
|
66
|
+
self._jump_table[ip] = w_ip
|
|
67
|
+
elif s.startswith('DO'):
|
|
68
|
+
do_stack.append(ip)
|
|
69
|
+
elif s.startswith('LOOP'):
|
|
70
|
+
if do_stack:
|
|
71
|
+
d_ip = do_stack.pop()
|
|
72
|
+
self._jump_table[d_ip] = ip
|
|
73
|
+
self._jump_table[ip] = d_ip
|
|
74
|
+
|
|
51
75
|
def _find_end_block(self, sorted_lines: list[int], start_ip: int, kind: str) -> int:
|
|
52
76
|
scan = start_ip + 1
|
|
53
77
|
end_re = RE_END_SUB if kind == 'SUB' else RE_END_FUNCTION
|
|
@@ -423,7 +423,6 @@ class QBasicTerminal(Engine, ExecutorMixin, ExpressionMixin, DisplayMixin, DemoM
|
|
|
423
423
|
# Program management
|
|
424
424
|
'CHECKSUM': 'cmd_checksum',
|
|
425
425
|
'VERSION': 'cmd_version',
|
|
426
|
-
'SEED': 'cmd_seed',
|
|
427
426
|
'PROBE': 'cmd_probe',
|
|
428
427
|
# Classic
|
|
429
428
|
'RESTORE': 'cmd_restore',
|
|
@@ -461,6 +460,7 @@ class QBasicTerminal(Engine, ExecutorMixin, ExpressionMixin, DisplayMixin, DemoM
|
|
|
461
460
|
# Circuit macros
|
|
462
461
|
'CIRCUIT_DEF': 'cmd_circuit_def', 'APPLY_CIRCUIT': 'cmd_apply_circuit',
|
|
463
462
|
'HELP': 'cmd_help', 'CONSISTENCY': 'cmd_consistency',
|
|
463
|
+
'SEED': 'cmd_seed',
|
|
464
464
|
}
|
|
465
465
|
|
|
466
466
|
def dispatch(self, line: str) -> None:
|
|
@@ -568,7 +568,7 @@ class QBasicTerminal(Engine, ExecutorMixin, ExpressionMixin, DisplayMixin, DemoM
|
|
|
568
568
|
gpu_ok = False
|
|
569
569
|
try:
|
|
570
570
|
_gb = AerSimulator(method='statevector', device='GPU')
|
|
571
|
-
_gb.run(transpile(
|
|
571
|
+
_gb.run(transpile(_pqc_m, _gb), shots=1).result()
|
|
572
572
|
gpu_ok = True
|
|
573
573
|
except Exception:
|
|
574
574
|
pass
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|