qubasic 0.1.0__py3-none-any.whl
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.
- qbasic.py +113 -0
- qbasic_core/__init__.py +80 -0
- qbasic_core/__main__.py +6 -0
- qbasic_core/analysis.py +179 -0
- qbasic_core/backend.py +76 -0
- qbasic_core/classic.py +419 -0
- qbasic_core/control_flow.py +576 -0
- qbasic_core/debug.py +327 -0
- qbasic_core/demos.py +356 -0
- qbasic_core/display.py +274 -0
- qbasic_core/engine.py +126 -0
- qbasic_core/engine_state.py +109 -0
- qbasic_core/errors.py +37 -0
- qbasic_core/exec_context.py +24 -0
- qbasic_core/executor.py +861 -0
- qbasic_core/expression.py +228 -0
- qbasic_core/file_io.py +457 -0
- qbasic_core/gates.py +284 -0
- qbasic_core/help_text.py +167 -0
- qbasic_core/io_protocol.py +33 -0
- qbasic_core/locc.py +10 -0
- qbasic_core/locc_commands.py +221 -0
- qbasic_core/locc_display.py +61 -0
- qbasic_core/locc_engine.py +195 -0
- qbasic_core/locc_execution.py +389 -0
- qbasic_core/memory.py +369 -0
- qbasic_core/mock_backend.py +66 -0
- qbasic_core/noise_mixin.py +96 -0
- qbasic_core/parser.py +564 -0
- qbasic_core/patterns.py +186 -0
- qbasic_core/profiler.py +156 -0
- qbasic_core/program_mgmt.py +369 -0
- qbasic_core/protocol.py +77 -0
- qbasic_core/py.typed +0 -0
- qbasic_core/scope.py +74 -0
- qbasic_core/screen.py +115 -0
- qbasic_core/state_display.py +60 -0
- qbasic_core/statements.py +387 -0
- qbasic_core/strings.py +107 -0
- qbasic_core/subs.py +261 -0
- qbasic_core/sweep.py +82 -0
- qbasic_core/terminal.py +1697 -0
- qubasic-0.1.0.dist-info/METADATA +736 -0
- qubasic-0.1.0.dist-info/RECORD +48 -0
- qubasic-0.1.0.dist-info/WHEEL +5 -0
- qubasic-0.1.0.dist-info/entry_points.txt +2 -0
- qubasic-0.1.0.dist-info/licenses/LICENSE +21 -0
- qubasic-0.1.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
"""Control flow helpers extracted from QBasicTerminal.
|
|
2
|
+
|
|
3
|
+
Requires: TerminalProtocol (see qbasic_core.protocol).
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
from typing import Any, Callable
|
|
10
|
+
|
|
11
|
+
from qbasic_core.engine import (
|
|
12
|
+
ExecResult, ExecOutcome,
|
|
13
|
+
RE_LET_ARRAY, RE_LET_VAR, RE_PRINT,
|
|
14
|
+
RE_GOTO, RE_GOSUB, RE_FOR, RE_NEXT, RE_WHILE, RE_IF_THEN,
|
|
15
|
+
)
|
|
16
|
+
from qbasic_core.parser import parse_stmt
|
|
17
|
+
from qbasic_core.statements import (
|
|
18
|
+
RawStmt, RemStmt, MeasureStmt, EndStmt, ReturnStmt, WendStmt,
|
|
19
|
+
LetArrayStmt, LetStmt, PrintStmt, GotoStmt, GosubStmt,
|
|
20
|
+
ForStmt, NextStmt, WhileStmt, IfThenStmt,
|
|
21
|
+
DataStmt, ReadStmt, OnGotoStmt, OnGosubStmt,
|
|
22
|
+
SelectCaseStmt, CaseStmt, EndSelectStmt,
|
|
23
|
+
DoStmt, LoopStmt, ExitStmt,
|
|
24
|
+
SwapStmt, DefFnStmt, OptionBaseStmt,
|
|
25
|
+
SubStmt, EndSubStmt, FunctionStmt, EndFunctionStmt, CallStmt,
|
|
26
|
+
LocalStmt, StaticStmt, SharedStmt,
|
|
27
|
+
OnErrorStmt, ResumeStmt, ErrorStmt, AssertStmt, StopStmt,
|
|
28
|
+
OnMeasureStmt, OnTimerStmt,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ControlFlowMixin:
|
|
33
|
+
"""Mixin providing control flow helpers for QBasicTerminal.
|
|
34
|
+
|
|
35
|
+
Requires: TerminalProtocol — uses self.program, self.variables,
|
|
36
|
+
self.arrays, self.locc_mode, self.locc, self._gosub_stack,
|
|
37
|
+
self._eval_with_vars(), self._eval_condition(), self._substitute_vars().
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
# ── Control flow helpers (decomposed from _exec_control_flow) ────
|
|
41
|
+
#
|
|
42
|
+
# Each _cf_* method accepts (self, stmt: str, ..., *, parsed=None).
|
|
43
|
+
# When parsed is provided the method uses the typed fields directly;
|
|
44
|
+
# when it is None the method falls back to regex matching on the raw
|
|
45
|
+
# string. The raw-string path is retained for backward compatibility
|
|
46
|
+
# but is no longer exercised by the main execution pipeline (callers
|
|
47
|
+
# now always supply a parsed Stmt via _exec_control_flow).
|
|
48
|
+
# Deferred cleanup: the raw-string fallback can be removed once all
|
|
49
|
+
# external call sites are confirmed to pass parsed objects.
|
|
50
|
+
|
|
51
|
+
def _cf_let_array(self, stmt: str, run_vars: dict[str, Any],
|
|
52
|
+
*, parsed: LetArrayStmt | None = None) -> tuple[bool, ExecOutcome] | None:
|
|
53
|
+
if parsed is None:
|
|
54
|
+
m = RE_LET_ARRAY.match(stmt)
|
|
55
|
+
if not m:
|
|
56
|
+
return None
|
|
57
|
+
name, idx_expr, val_expr = m.group(1), m.group(2), m.group(3)
|
|
58
|
+
else:
|
|
59
|
+
name, idx_expr, val_expr = parsed.name, parsed.index_expr, parsed.value_expr
|
|
60
|
+
idx = int(self._eval_with_vars(idx_expr, run_vars))
|
|
61
|
+
val = self._eval_with_vars(val_expr, run_vars)
|
|
62
|
+
if name not in self.arrays:
|
|
63
|
+
self.arrays[name] = [0.0] * (idx + 1)
|
|
64
|
+
while idx >= len(self.arrays[name]):
|
|
65
|
+
self.arrays[name].append(0.0)
|
|
66
|
+
self.arrays[name][idx] = val
|
|
67
|
+
return True, ExecResult.ADVANCE
|
|
68
|
+
|
|
69
|
+
def _cf_let_var(self, stmt: str, run_vars: dict[str, Any],
|
|
70
|
+
*, parsed: LetStmt | None = None) -> tuple[bool, ExecOutcome] | None:
|
|
71
|
+
if parsed is None:
|
|
72
|
+
m = RE_LET_VAR.match(stmt)
|
|
73
|
+
if not m:
|
|
74
|
+
return None
|
|
75
|
+
name, expr = m.group(1), m.group(2)
|
|
76
|
+
else:
|
|
77
|
+
name, expr = parsed.name, parsed.expr
|
|
78
|
+
val = self._eval_with_vars(expr, run_vars)
|
|
79
|
+
run_vars[name] = val
|
|
80
|
+
self.variables[name] = val
|
|
81
|
+
return True, ExecResult.ADVANCE
|
|
82
|
+
|
|
83
|
+
def _cf_print(self, stmt: str, run_vars: dict[str, Any],
|
|
84
|
+
*, parsed: PrintStmt | None = None) -> tuple[bool, ExecOutcome] | None:
|
|
85
|
+
if parsed is None:
|
|
86
|
+
m = RE_PRINT.match(stmt)
|
|
87
|
+
if not m:
|
|
88
|
+
return None
|
|
89
|
+
raw_expr = m.group(1)
|
|
90
|
+
else:
|
|
91
|
+
raw_expr = parsed.expr
|
|
92
|
+
text = self._substitute_vars(raw_expr.strip(), run_vars)
|
|
93
|
+
# Determine trailing separator: ; suppresses newline, , advances to tab
|
|
94
|
+
suppress_newline = raw_expr.rstrip().endswith(';')
|
|
95
|
+
tab_advance = raw_expr.rstrip().endswith(',')
|
|
96
|
+
if suppress_newline:
|
|
97
|
+
text = text.rstrip().removesuffix(';').rstrip()
|
|
98
|
+
elif tab_advance:
|
|
99
|
+
text = text.rstrip().removesuffix(',').rstrip()
|
|
100
|
+
# Evaluate SPC(n) and TAB(n) inline
|
|
101
|
+
def _replace_spc(m_spc):
|
|
102
|
+
n = int(self._eval_with_vars(m_spc.group(1), run_vars))
|
|
103
|
+
return ' ' * max(0, n)
|
|
104
|
+
def _replace_tab(m_tab):
|
|
105
|
+
n = int(self._eval_with_vars(m_tab.group(1), run_vars))
|
|
106
|
+
return ' ' * max(0, n)
|
|
107
|
+
text = re.sub(r'\bSPC\s*\(([^)]+)\)', _replace_spc, text, flags=re.IGNORECASE)
|
|
108
|
+
text = re.sub(r'\bTAB\s*\(([^)]+)\)', _replace_tab, text, flags=re.IGNORECASE)
|
|
109
|
+
# Evaluate the expression
|
|
110
|
+
if (text.startswith('"') and text.endswith('"')) or \
|
|
111
|
+
(text.startswith("'") and text.endswith("'")):
|
|
112
|
+
output = text[1:-1]
|
|
113
|
+
else:
|
|
114
|
+
try:
|
|
115
|
+
ns = run_vars.as_dict() if hasattr(run_vars, 'as_dict') else dict(run_vars) if not isinstance(run_vars, dict) else run_vars
|
|
116
|
+
result = self._safe_eval(text, extra_ns=ns)
|
|
117
|
+
output = str(result)
|
|
118
|
+
except Exception:
|
|
119
|
+
output = text
|
|
120
|
+
# Output with separator behavior
|
|
121
|
+
if suppress_newline:
|
|
122
|
+
self.io.write(output)
|
|
123
|
+
elif tab_advance:
|
|
124
|
+
col = len(output) % 14
|
|
125
|
+
padding = 14 - col if col > 0 else 14
|
|
126
|
+
self.io.write(output + ' ' * padding)
|
|
127
|
+
else:
|
|
128
|
+
self.io.writeln(output)
|
|
129
|
+
return True, ExecResult.ADVANCE
|
|
130
|
+
|
|
131
|
+
def _cf_goto(self, stmt: str, sorted_lines: list[int],
|
|
132
|
+
*, parsed: GotoStmt | None = None) -> tuple[bool, int] | None:
|
|
133
|
+
if parsed is None:
|
|
134
|
+
m = RE_GOTO.match(stmt)
|
|
135
|
+
if not m:
|
|
136
|
+
return None
|
|
137
|
+
target = int(m.group(1))
|
|
138
|
+
else:
|
|
139
|
+
target = parsed.target
|
|
140
|
+
for idx, ln in enumerate(sorted_lines):
|
|
141
|
+
if ln == target:
|
|
142
|
+
return True, idx
|
|
143
|
+
raise RuntimeError(f"GOTO {target}: LINE NOT FOUND")
|
|
144
|
+
|
|
145
|
+
def _cf_gosub(self, stmt: str, sorted_lines: list[int], ip: int,
|
|
146
|
+
*, parsed: GosubStmt | None = None) -> tuple[bool, int] | None:
|
|
147
|
+
if parsed is None:
|
|
148
|
+
m = RE_GOSUB.match(stmt)
|
|
149
|
+
if not m:
|
|
150
|
+
return None
|
|
151
|
+
target = int(m.group(1))
|
|
152
|
+
else:
|
|
153
|
+
target = parsed.target
|
|
154
|
+
self._gosub_stack.append(ip + 1)
|
|
155
|
+
for idx, ln in enumerate(sorted_lines):
|
|
156
|
+
if ln == target:
|
|
157
|
+
return True, idx
|
|
158
|
+
raise RuntimeError(f"GOSUB {target}: LINE NOT FOUND")
|
|
159
|
+
|
|
160
|
+
def _cf_for(self, stmt: str, run_vars: dict[str, Any], loop_stack: list[dict[str, Any]], ip: int,
|
|
161
|
+
*, parsed: ForStmt | None = None) -> tuple[bool, ExecOutcome] | None:
|
|
162
|
+
if parsed is None:
|
|
163
|
+
m = RE_FOR.match(stmt)
|
|
164
|
+
if not m:
|
|
165
|
+
return None
|
|
166
|
+
var = m.group(1)
|
|
167
|
+
start_expr, end_expr, step_expr = m.group(2), m.group(3), m.group(4)
|
|
168
|
+
else:
|
|
169
|
+
var = parsed.var
|
|
170
|
+
start_expr, end_expr, step_expr = parsed.start_expr, parsed.end_expr, parsed.step_expr
|
|
171
|
+
start = self._eval_with_vars(start_expr, run_vars)
|
|
172
|
+
end = self._eval_with_vars(end_expr, run_vars)
|
|
173
|
+
step = self._eval_with_vars(step_expr, run_vars) if step_expr else 1
|
|
174
|
+
try:
|
|
175
|
+
if start == int(start): start = int(start)
|
|
176
|
+
except (OverflowError, ValueError):
|
|
177
|
+
pass
|
|
178
|
+
try:
|
|
179
|
+
if end == int(end): end = int(end)
|
|
180
|
+
except (OverflowError, ValueError):
|
|
181
|
+
pass
|
|
182
|
+
try:
|
|
183
|
+
if isinstance(step, float) and step == int(step): step = int(step)
|
|
184
|
+
except (OverflowError, ValueError):
|
|
185
|
+
pass
|
|
186
|
+
run_vars[var] = start
|
|
187
|
+
self.variables[var] = start
|
|
188
|
+
loop_stack.append({'var': var, 'current': start, 'end': end,
|
|
189
|
+
'step': step, 'return_ip': ip})
|
|
190
|
+
return True, ExecResult.ADVANCE
|
|
191
|
+
|
|
192
|
+
def _cf_next(self, stmt: str, run_vars: dict[str, Any], loop_stack: list[dict[str, Any]],
|
|
193
|
+
*, parsed: NextStmt | None = None) -> tuple[bool, ExecOutcome] | None:
|
|
194
|
+
if parsed is None:
|
|
195
|
+
m = RE_NEXT.match(stmt)
|
|
196
|
+
if not m:
|
|
197
|
+
return None
|
|
198
|
+
var = m.group(1)
|
|
199
|
+
else:
|
|
200
|
+
var = parsed.var
|
|
201
|
+
if not loop_stack or loop_stack[-1].get('var') != var:
|
|
202
|
+
if loop_stack:
|
|
203
|
+
expected = loop_stack[-1].get('var', '?')
|
|
204
|
+
raise RuntimeError(f"NEXT {var} does not match current FOR {expected}")
|
|
205
|
+
raise RuntimeError(f"NEXT {var} without matching FOR")
|
|
206
|
+
loop = loop_stack[-1]
|
|
207
|
+
loop['current'] += loop['step']
|
|
208
|
+
if (loop['step'] > 0 and loop['current'] <= loop['end']) or \
|
|
209
|
+
(loop['step'] < 0 and loop['current'] >= loop['end']):
|
|
210
|
+
run_vars[var] = loop['current']
|
|
211
|
+
self.variables[var] = loop['current']
|
|
212
|
+
return True, loop['return_ip'] + 1
|
|
213
|
+
else:
|
|
214
|
+
loop_stack.pop()
|
|
215
|
+
return True, ExecResult.ADVANCE
|
|
216
|
+
|
|
217
|
+
def _find_matching_wend(self, sorted_lines: list[int], ip: int) -> int:
|
|
218
|
+
"""Find the ip after the WEND matching the WHILE at ip.
|
|
219
|
+
|
|
220
|
+
Scans forward with proper nesting depth tracking. Returns the ip
|
|
221
|
+
index past the matching WEND. Raises with the WHILE line number
|
|
222
|
+
for clear diagnostics.
|
|
223
|
+
"""
|
|
224
|
+
depth = 1
|
|
225
|
+
scan = ip + 1
|
|
226
|
+
while scan < len(sorted_lines):
|
|
227
|
+
s = self.program[sorted_lines[scan]].strip().upper()
|
|
228
|
+
if s.startswith('WHILE '):
|
|
229
|
+
depth += 1
|
|
230
|
+
elif s == 'WEND':
|
|
231
|
+
depth -= 1
|
|
232
|
+
if depth == 0:
|
|
233
|
+
return scan + 1
|
|
234
|
+
scan += 1
|
|
235
|
+
line_num = sorted_lines[ip]
|
|
236
|
+
raise RuntimeError(f"WHILE at line {line_num} has no matching WEND")
|
|
237
|
+
|
|
238
|
+
def _cf_while(self, stmt: str, run_vars: dict[str, Any], loop_stack: list[dict[str, Any]],
|
|
239
|
+
sorted_lines: list[int], ip: int,
|
|
240
|
+
*, parsed: WhileStmt | None = None) -> tuple[bool, ExecOutcome] | None:
|
|
241
|
+
if parsed is None:
|
|
242
|
+
m = RE_WHILE.match(stmt)
|
|
243
|
+
if not m:
|
|
244
|
+
return None
|
|
245
|
+
cond = m.group(1).strip()
|
|
246
|
+
else:
|
|
247
|
+
cond = parsed.condition
|
|
248
|
+
if self._eval_condition(cond, run_vars):
|
|
249
|
+
loop_stack.append({'type': 'while', 'cond': cond, 'return_ip': ip})
|
|
250
|
+
return True, ExecResult.ADVANCE
|
|
251
|
+
else:
|
|
252
|
+
return True, self._find_matching_wend(sorted_lines, ip)
|
|
253
|
+
|
|
254
|
+
def _cf_wend(self, run_vars: dict[str, Any], loop_stack: list[dict[str, Any]],
|
|
255
|
+
sorted_lines: list[int] | None = None, ip: int | None = None) -> tuple[bool, ExecOutcome] | None:
|
|
256
|
+
if not loop_stack or loop_stack[-1].get('type') != 'while':
|
|
257
|
+
ctx = f" at line {sorted_lines[ip]}" if sorted_lines and ip is not None else ""
|
|
258
|
+
raise RuntimeError(f"WEND{ctx} without matching WHILE")
|
|
259
|
+
loop = loop_stack[-1]
|
|
260
|
+
if self._eval_condition(loop['cond'], run_vars):
|
|
261
|
+
return True, loop['return_ip']
|
|
262
|
+
else:
|
|
263
|
+
loop_stack.pop()
|
|
264
|
+
return True, ExecResult.ADVANCE
|
|
265
|
+
|
|
266
|
+
def _cf_if_then(self, stmt: str, run_vars: dict[str, Any], loop_stack: list[dict[str, Any]],
|
|
267
|
+
sorted_lines: list[int], ip: int,
|
|
268
|
+
exec_fn: Callable[..., Any],
|
|
269
|
+
*, parsed: IfThenStmt | None = None) -> tuple[bool, ExecOutcome] | None:
|
|
270
|
+
if parsed is None:
|
|
271
|
+
m = RE_IF_THEN.match(stmt)
|
|
272
|
+
if not m:
|
|
273
|
+
return None
|
|
274
|
+
cond_str = m.group(1).strip()
|
|
275
|
+
then_clause = m.group(2).strip()
|
|
276
|
+
else_clause = m.group(3).strip() if m.group(3) else None
|
|
277
|
+
else:
|
|
278
|
+
cond_str = parsed.condition
|
|
279
|
+
then_clause = parsed.then_clause
|
|
280
|
+
else_clause = parsed.else_clause
|
|
281
|
+
cond_vars = run_vars
|
|
282
|
+
if self.locc_mode and self.locc:
|
|
283
|
+
cond_vars = {**run_vars, **self.locc.classical}
|
|
284
|
+
result = ExecResult.ADVANCE
|
|
285
|
+
if self._eval_condition(cond_str, cond_vars):
|
|
286
|
+
if then_clause:
|
|
287
|
+
r = exec_fn(then_clause, loop_stack, sorted_lines, ip, run_vars)
|
|
288
|
+
if r is not None and r is not ExecResult.ADVANCE:
|
|
289
|
+
result = r
|
|
290
|
+
elif else_clause:
|
|
291
|
+
r = exec_fn(else_clause, loop_stack, sorted_lines, ip, run_vars)
|
|
292
|
+
if r is not None and r is not ExecResult.ADVANCE:
|
|
293
|
+
result = r
|
|
294
|
+
return True, result
|
|
295
|
+
|
|
296
|
+
# ── Type-based dispatch table ────────────────────────────────────
|
|
297
|
+
# Maps parsed Stmt types to handler functions. Each handler
|
|
298
|
+
# receives (self, stmt, parsed, loop_stack, sorted_lines, ip,
|
|
299
|
+
# run_vars, exec_fn) and returns (handled: bool, result). Built
|
|
300
|
+
# once as a class variable so the dict lookup cost is paid
|
|
301
|
+
# per-call, not per-class.
|
|
302
|
+
|
|
303
|
+
@staticmethod
|
|
304
|
+
def _d_rem(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
305
|
+
return True, ExecResult.ADVANCE
|
|
306
|
+
|
|
307
|
+
_d_measure = _d_rem # same behavior
|
|
308
|
+
|
|
309
|
+
@staticmethod
|
|
310
|
+
def _d_end(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
311
|
+
return True, ExecResult.END
|
|
312
|
+
|
|
313
|
+
@staticmethod
|
|
314
|
+
def _d_return(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
315
|
+
if not self._gosub_stack:
|
|
316
|
+
raise RuntimeError("RETURN WITHOUT GOSUB")
|
|
317
|
+
return True, self._gosub_stack.pop()
|
|
318
|
+
|
|
319
|
+
@staticmethod
|
|
320
|
+
def _d_wend(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
321
|
+
return self._cf_wend(run_vars, loop_stack, sorted_lines, ip)
|
|
322
|
+
|
|
323
|
+
@staticmethod
|
|
324
|
+
def _d_let_array(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
325
|
+
r = self._cf_let_array(stmt, run_vars, parsed=parsed)
|
|
326
|
+
return r if r is not None else (False, None)
|
|
327
|
+
|
|
328
|
+
@staticmethod
|
|
329
|
+
def _d_let_var(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
330
|
+
r = self._cf_let_var(stmt, run_vars, parsed=parsed)
|
|
331
|
+
return r if r is not None else (False, None)
|
|
332
|
+
|
|
333
|
+
@staticmethod
|
|
334
|
+
def _d_print(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
335
|
+
r = self._cf_print(stmt, run_vars, parsed=parsed)
|
|
336
|
+
return r if r is not None else (False, None)
|
|
337
|
+
|
|
338
|
+
@staticmethod
|
|
339
|
+
def _d_goto(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
340
|
+
r = self._cf_goto(stmt, sorted_lines, parsed=parsed)
|
|
341
|
+
return r if r is not None else (False, None)
|
|
342
|
+
|
|
343
|
+
@staticmethod
|
|
344
|
+
def _d_gosub(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
345
|
+
r = self._cf_gosub(stmt, sorted_lines, ip, parsed=parsed)
|
|
346
|
+
return r if r is not None else (False, None)
|
|
347
|
+
|
|
348
|
+
@staticmethod
|
|
349
|
+
def _d_for(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
350
|
+
r = self._cf_for(stmt, run_vars, loop_stack, ip, parsed=parsed)
|
|
351
|
+
return r if r is not None else (False, None)
|
|
352
|
+
|
|
353
|
+
@staticmethod
|
|
354
|
+
def _d_next(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
355
|
+
r = self._cf_next(stmt, run_vars, loop_stack, parsed=parsed)
|
|
356
|
+
return r if r is not None else (False, None)
|
|
357
|
+
|
|
358
|
+
@staticmethod
|
|
359
|
+
def _d_while(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
360
|
+
r = self._cf_while(stmt, run_vars, loop_stack, sorted_lines, ip, parsed=parsed)
|
|
361
|
+
return r if r is not None else (False, None)
|
|
362
|
+
|
|
363
|
+
@staticmethod
|
|
364
|
+
def _d_if_then(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
365
|
+
r = self._cf_if_then(stmt, run_vars, loop_stack, sorted_lines, ip, exec_fn, parsed=parsed)
|
|
366
|
+
return r if r is not None else (False, None)
|
|
367
|
+
|
|
368
|
+
@staticmethod
|
|
369
|
+
def _d_data(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
370
|
+
r = self._cf_data(stmt, parsed=parsed)
|
|
371
|
+
return r if r is not None else (False, None)
|
|
372
|
+
|
|
373
|
+
@staticmethod
|
|
374
|
+
def _d_read(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
375
|
+
r = self._cf_read(stmt, run_vars, parsed=parsed)
|
|
376
|
+
return r if r is not None else (False, None)
|
|
377
|
+
|
|
378
|
+
@staticmethod
|
|
379
|
+
def _d_on_goto(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
380
|
+
r = self._cf_on_goto(stmt, run_vars, sorted_lines, parsed=parsed)
|
|
381
|
+
return r if r is not None else (False, None)
|
|
382
|
+
|
|
383
|
+
@staticmethod
|
|
384
|
+
def _d_on_gosub(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
385
|
+
r = self._cf_on_gosub(stmt, run_vars, sorted_lines, ip, parsed=parsed)
|
|
386
|
+
return r if r is not None else (False, None)
|
|
387
|
+
|
|
388
|
+
@staticmethod
|
|
389
|
+
def _d_select_case(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
390
|
+
r = self._cf_select_case(stmt, run_vars, sorted_lines, ip, parsed=parsed)
|
|
391
|
+
return r if r is not None else (False, None)
|
|
392
|
+
|
|
393
|
+
@staticmethod
|
|
394
|
+
def _d_case(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
395
|
+
r = self._cf_case(stmt, sorted_lines, ip, parsed=parsed)
|
|
396
|
+
return r if r is not None else (False, None)
|
|
397
|
+
|
|
398
|
+
@staticmethod
|
|
399
|
+
def _d_end_select(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
400
|
+
r = self._cf_end_select(stmt, parsed=parsed)
|
|
401
|
+
return r if r is not None else (False, None)
|
|
402
|
+
|
|
403
|
+
@staticmethod
|
|
404
|
+
def _d_do(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
405
|
+
r = self._cf_do(stmt, run_vars, loop_stack, sorted_lines, ip, parsed=parsed)
|
|
406
|
+
return r if r is not None else (False, None)
|
|
407
|
+
|
|
408
|
+
@staticmethod
|
|
409
|
+
def _d_loop(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
410
|
+
r = self._cf_loop(stmt, run_vars, loop_stack, sorted_lines, ip, parsed=parsed)
|
|
411
|
+
return r if r is not None else (False, None)
|
|
412
|
+
|
|
413
|
+
@staticmethod
|
|
414
|
+
def _d_exit(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
415
|
+
r = self._cf_exit(stmt, loop_stack, sorted_lines, ip, parsed=parsed)
|
|
416
|
+
return r if r is not None else (False, None)
|
|
417
|
+
|
|
418
|
+
@staticmethod
|
|
419
|
+
def _d_swap(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
420
|
+
r = self._cf_swap(stmt, run_vars, parsed=parsed)
|
|
421
|
+
return r if r is not None else (False, None)
|
|
422
|
+
|
|
423
|
+
@staticmethod
|
|
424
|
+
def _d_def_fn(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
425
|
+
r = self._cf_def_fn(stmt, run_vars, parsed=parsed)
|
|
426
|
+
return r if r is not None else (False, None)
|
|
427
|
+
|
|
428
|
+
@staticmethod
|
|
429
|
+
def _d_option_base(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
430
|
+
r = self._cf_option_base(stmt, parsed=parsed)
|
|
431
|
+
return r if r is not None else (False, None)
|
|
432
|
+
|
|
433
|
+
@staticmethod
|
|
434
|
+
def _d_sub(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
435
|
+
r = self._cf_sub(stmt, sorted_lines, ip, parsed=parsed)
|
|
436
|
+
return r if r is not None else (False, None)
|
|
437
|
+
|
|
438
|
+
@staticmethod
|
|
439
|
+
def _d_end_sub(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
440
|
+
r = self._cf_end_sub(stmt, parsed=parsed)
|
|
441
|
+
return r if r is not None else (False, None)
|
|
442
|
+
|
|
443
|
+
@staticmethod
|
|
444
|
+
def _d_function(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
445
|
+
r = self._cf_function(stmt, sorted_lines, ip, parsed=parsed)
|
|
446
|
+
return r if r is not None else (False, None)
|
|
447
|
+
|
|
448
|
+
@staticmethod
|
|
449
|
+
def _d_end_function(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
450
|
+
r = self._cf_end_function(stmt, parsed=parsed)
|
|
451
|
+
return r if r is not None else (False, None)
|
|
452
|
+
|
|
453
|
+
@staticmethod
|
|
454
|
+
def _d_call(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
455
|
+
r = self._cf_call(stmt, run_vars, sorted_lines, ip, parsed=parsed)
|
|
456
|
+
return r if r is not None else (False, None)
|
|
457
|
+
|
|
458
|
+
@staticmethod
|
|
459
|
+
def _d_local(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
460
|
+
r = self._cf_local(stmt, run_vars, parsed=parsed)
|
|
461
|
+
return r if r is not None else (False, None)
|
|
462
|
+
|
|
463
|
+
@staticmethod
|
|
464
|
+
def _d_static(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
465
|
+
r = self._cf_static(stmt, run_vars, parsed=parsed)
|
|
466
|
+
return r if r is not None else (False, None)
|
|
467
|
+
|
|
468
|
+
@staticmethod
|
|
469
|
+
def _d_shared(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
470
|
+
r = self._cf_shared(stmt, run_vars, parsed=parsed)
|
|
471
|
+
return r if r is not None else (False, None)
|
|
472
|
+
|
|
473
|
+
@staticmethod
|
|
474
|
+
def _d_on_error(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
475
|
+
r = self._cf_on_error(stmt, parsed=parsed)
|
|
476
|
+
return r if r is not None else (False, None)
|
|
477
|
+
|
|
478
|
+
@staticmethod
|
|
479
|
+
def _d_resume(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
480
|
+
r = self._cf_resume(stmt, sorted_lines, parsed=parsed)
|
|
481
|
+
return r if r is not None else (False, None)
|
|
482
|
+
|
|
483
|
+
@staticmethod
|
|
484
|
+
def _d_error(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
485
|
+
r = self._cf_error(stmt, parsed=parsed)
|
|
486
|
+
return r if r is not None else (False, None)
|
|
487
|
+
|
|
488
|
+
@staticmethod
|
|
489
|
+
def _d_assert(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
490
|
+
r = self._cf_assert(stmt, run_vars, parsed=parsed)
|
|
491
|
+
return r if r is not None else (False, None)
|
|
492
|
+
|
|
493
|
+
@staticmethod
|
|
494
|
+
def _d_stop(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
495
|
+
r = self._cf_stop(stmt, sorted_lines, ip, parsed=parsed)
|
|
496
|
+
return r if r is not None else (False, None)
|
|
497
|
+
|
|
498
|
+
@staticmethod
|
|
499
|
+
def _d_on_measure(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
500
|
+
r = self._cf_on_measure(stmt, parsed=parsed)
|
|
501
|
+
return r if r is not None else (False, None)
|
|
502
|
+
|
|
503
|
+
@staticmethod
|
|
504
|
+
def _d_on_timer(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn):
|
|
505
|
+
r = self._cf_on_timer(stmt, parsed=parsed)
|
|
506
|
+
return r if r is not None else (False, None)
|
|
507
|
+
|
|
508
|
+
_CF_DISPATCH: dict[type, Callable] = {
|
|
509
|
+
RemStmt: _d_rem,
|
|
510
|
+
MeasureStmt: _d_measure,
|
|
511
|
+
EndStmt: _d_end,
|
|
512
|
+
ReturnStmt: _d_return,
|
|
513
|
+
WendStmt: _d_wend,
|
|
514
|
+
LetArrayStmt: _d_let_array,
|
|
515
|
+
LetStmt: _d_let_var,
|
|
516
|
+
PrintStmt: _d_print,
|
|
517
|
+
GotoStmt: _d_goto,
|
|
518
|
+
GosubStmt: _d_gosub,
|
|
519
|
+
ForStmt: _d_for,
|
|
520
|
+
NextStmt: _d_next,
|
|
521
|
+
WhileStmt: _d_while,
|
|
522
|
+
IfThenStmt: _d_if_then,
|
|
523
|
+
DataStmt: _d_data,
|
|
524
|
+
ReadStmt: _d_read,
|
|
525
|
+
OnGotoStmt: _d_on_goto,
|
|
526
|
+
OnGosubStmt: _d_on_gosub,
|
|
527
|
+
SelectCaseStmt: _d_select_case,
|
|
528
|
+
CaseStmt: _d_case,
|
|
529
|
+
EndSelectStmt: _d_end_select,
|
|
530
|
+
DoStmt: _d_do,
|
|
531
|
+
LoopStmt: _d_loop,
|
|
532
|
+
ExitStmt: _d_exit,
|
|
533
|
+
SwapStmt: _d_swap,
|
|
534
|
+
DefFnStmt: _d_def_fn,
|
|
535
|
+
OptionBaseStmt: _d_option_base,
|
|
536
|
+
SubStmt: _d_sub,
|
|
537
|
+
EndSubStmt: _d_end_sub,
|
|
538
|
+
FunctionStmt: _d_function,
|
|
539
|
+
EndFunctionStmt: _d_end_function,
|
|
540
|
+
CallStmt: _d_call,
|
|
541
|
+
LocalStmt: _d_local,
|
|
542
|
+
StaticStmt: _d_static,
|
|
543
|
+
SharedStmt: _d_shared,
|
|
544
|
+
OnErrorStmt: _d_on_error,
|
|
545
|
+
ResumeStmt: _d_resume,
|
|
546
|
+
ErrorStmt: _d_error,
|
|
547
|
+
AssertStmt: _d_assert,
|
|
548
|
+
StopStmt: _d_stop,
|
|
549
|
+
OnMeasureStmt: _d_on_measure,
|
|
550
|
+
OnTimerStmt: _d_on_timer,
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
def _exec_control_flow(
|
|
554
|
+
self, stmt: str, loop_stack: list[dict[str, Any]],
|
|
555
|
+
sorted_lines: list[int], ip: int, run_vars: dict[str, Any],
|
|
556
|
+
exec_fn: Callable[..., Any],
|
|
557
|
+
*, parsed=None,
|
|
558
|
+
) -> tuple[bool, ExecOutcome | None]:
|
|
559
|
+
"""Shared control flow for both Qiskit and LOCC execution paths.
|
|
560
|
+
Returns (handled, result) — if handled is True, result is the return value.
|
|
561
|
+
exec_fn is the recursive line executor for IF/multi-statement dispatch.
|
|
562
|
+
|
|
563
|
+
Dispatches via dict lookup on the parsed Stmt type (O(1)) instead
|
|
564
|
+
of a linear chain of regex-matching _cf_* calls.
|
|
565
|
+
|
|
566
|
+
If *parsed* is provided, the parse_stmt call is skipped (avoids
|
|
567
|
+
redundant parsing when the caller has already parsed the statement).
|
|
568
|
+
"""
|
|
569
|
+
if parsed is None:
|
|
570
|
+
parsed = parse_stmt(stmt)
|
|
571
|
+
handler = self._CF_DISPATCH.get(type(parsed))
|
|
572
|
+
if handler is not None:
|
|
573
|
+
return handler(self, stmt, parsed, loop_stack, sorted_lines, ip, run_vars, exec_fn)
|
|
574
|
+
|
|
575
|
+
# RawStmt or unmapped type — not handled by control flow
|
|
576
|
+
return False, None
|