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.
Files changed (48) hide show
  1. qbasic.py +113 -0
  2. qbasic_core/__init__.py +80 -0
  3. qbasic_core/__main__.py +6 -0
  4. qbasic_core/analysis.py +179 -0
  5. qbasic_core/backend.py +76 -0
  6. qbasic_core/classic.py +419 -0
  7. qbasic_core/control_flow.py +576 -0
  8. qbasic_core/debug.py +327 -0
  9. qbasic_core/demos.py +356 -0
  10. qbasic_core/display.py +274 -0
  11. qbasic_core/engine.py +126 -0
  12. qbasic_core/engine_state.py +109 -0
  13. qbasic_core/errors.py +37 -0
  14. qbasic_core/exec_context.py +24 -0
  15. qbasic_core/executor.py +861 -0
  16. qbasic_core/expression.py +228 -0
  17. qbasic_core/file_io.py +457 -0
  18. qbasic_core/gates.py +284 -0
  19. qbasic_core/help_text.py +167 -0
  20. qbasic_core/io_protocol.py +33 -0
  21. qbasic_core/locc.py +10 -0
  22. qbasic_core/locc_commands.py +221 -0
  23. qbasic_core/locc_display.py +61 -0
  24. qbasic_core/locc_engine.py +195 -0
  25. qbasic_core/locc_execution.py +389 -0
  26. qbasic_core/memory.py +369 -0
  27. qbasic_core/mock_backend.py +66 -0
  28. qbasic_core/noise_mixin.py +96 -0
  29. qbasic_core/parser.py +564 -0
  30. qbasic_core/patterns.py +186 -0
  31. qbasic_core/profiler.py +156 -0
  32. qbasic_core/program_mgmt.py +369 -0
  33. qbasic_core/protocol.py +77 -0
  34. qbasic_core/py.typed +0 -0
  35. qbasic_core/scope.py +74 -0
  36. qbasic_core/screen.py +115 -0
  37. qbasic_core/state_display.py +60 -0
  38. qbasic_core/statements.py +387 -0
  39. qbasic_core/strings.py +107 -0
  40. qbasic_core/subs.py +261 -0
  41. qbasic_core/sweep.py +82 -0
  42. qbasic_core/terminal.py +1697 -0
  43. qubasic-0.1.0.dist-info/METADATA +736 -0
  44. qubasic-0.1.0.dist-info/RECORD +48 -0
  45. qubasic-0.1.0.dist-info/WHEEL +5 -0
  46. qubasic-0.1.0.dist-info/entry_points.txt +2 -0
  47. qubasic-0.1.0.dist-info/licenses/LICENSE +21 -0
  48. qubasic-0.1.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,60 @@
1
+ """QBASIC state display mixin — STATE, BLOCH, HIST, PROBS commands."""
2
+
3
+ from qbasic_core.engine import MAX_BLOCH_DISPLAY
4
+
5
+
6
+ class StateDisplayMixin:
7
+ """State inspection commands for QBasicTerminal.
8
+
9
+ Requires: TerminalProtocol — uses self.locc_mode, self.last_sv,
10
+ self.last_counts, self.last_circuit, self.num_qubits,
11
+ self._locc_state(), self._locc_bloch(),
12
+ self._print_statevector(), self._print_probs(), self._print_bloch_single(),
13
+ self.print_histogram(), self.io.
14
+ """
15
+
16
+ def cmd_state(self, rest: str = '') -> None:
17
+ if self.locc_mode:
18
+ return self._locc_state(rest)
19
+ if self.last_sv is None:
20
+ self.io.writeln("?NO STATE — RUN first")
21
+ return
22
+ self._print_statevector(self.last_sv)
23
+
24
+ def cmd_hist(self) -> None:
25
+ if self.last_counts is None:
26
+ self.io.writeln("?NO RESULTS — RUN first")
27
+ return
28
+ self.print_histogram(self.last_counts)
29
+
30
+ def cmd_probs(self) -> None:
31
+ if self.last_sv is None:
32
+ self.io.writeln("?NO STATE — RUN first")
33
+ return
34
+ self._print_probs(self.last_sv)
35
+
36
+ def cmd_bloch(self, rest: str) -> None:
37
+ if self.locc_mode:
38
+ return self._locc_bloch(rest)
39
+ if self.last_sv is None:
40
+ self.io.writeln("?NO STATE — RUN first")
41
+ return
42
+ if rest:
43
+ q = int(rest)
44
+ self._print_bloch_single(self.last_sv, q)
45
+ else:
46
+ for q in range(min(self.num_qubits, MAX_BLOCH_DISPLAY)):
47
+ self._print_bloch_single(self.last_sv, q)
48
+ if q < min(self.num_qubits, MAX_BLOCH_DISPLAY) - 1:
49
+ self.io.writeln('')
50
+
51
+ def cmd_circuit(self) -> None:
52
+ if self.last_circuit is None:
53
+ self.io.writeln("?NO CIRCUIT — RUN first")
54
+ return
55
+ try:
56
+ self.io.writeln(self.last_circuit.draw(output='text'))
57
+ except Exception:
58
+ self.io.writeln(f"Circuit: {self.last_circuit.num_qubits} qubits, "
59
+ f"depth {self.last_circuit.depth()}, "
60
+ f"{self.last_circuit.size()} gates")
@@ -0,0 +1,387 @@
1
+ """QBASIC typed statement AST — produced by the parser, consumed by the executor."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+
8
+ @dataclass(frozen=True, slots=True)
9
+ class Stmt:
10
+ """Base for all parsed statements."""
11
+ raw: str
12
+
13
+
14
+ # ── Terminals ──────────────────────────────────────────────────────────
15
+
16
+ @dataclass(frozen=True, slots=True)
17
+ class RemStmt(Stmt):
18
+ pass
19
+
20
+ @dataclass(frozen=True, slots=True)
21
+ class MeasureStmt(Stmt):
22
+ pass
23
+
24
+ @dataclass(frozen=True, slots=True)
25
+ class EndStmt(Stmt):
26
+ pass
27
+
28
+ @dataclass(frozen=True, slots=True)
29
+ class ReturnStmt(Stmt):
30
+ pass
31
+
32
+ @dataclass(frozen=True, slots=True)
33
+ class BarrierStmt(Stmt):
34
+ pass
35
+
36
+ @dataclass(frozen=True, slots=True)
37
+ class WendStmt(Stmt):
38
+ pass
39
+
40
+ @dataclass(frozen=True, slots=True)
41
+ class RestoreStmt(Stmt):
42
+ pass
43
+
44
+ @dataclass(frozen=True, slots=True)
45
+ class EndSelectStmt(Stmt):
46
+ pass
47
+
48
+ @dataclass(frozen=True, slots=True)
49
+ class EndSubStmt(Stmt):
50
+ pass
51
+
52
+ @dataclass(frozen=True, slots=True)
53
+ class EndFunctionStmt(Stmt):
54
+ pass
55
+
56
+ @dataclass(frozen=True, slots=True)
57
+ class StopStmt(Stmt):
58
+ pass
59
+
60
+
61
+ # ── Control flow ───────────────────────────────────────────────────────
62
+
63
+ @dataclass(frozen=True, slots=True)
64
+ class GotoStmt(Stmt):
65
+ target: int
66
+
67
+ @dataclass(frozen=True, slots=True)
68
+ class GosubStmt(Stmt):
69
+ target: int
70
+
71
+ @dataclass(frozen=True, slots=True)
72
+ class ForStmt(Stmt):
73
+ var: str
74
+ start_expr: str
75
+ end_expr: str
76
+ step_expr: str | None
77
+
78
+ @dataclass(frozen=True, slots=True)
79
+ class NextStmt(Stmt):
80
+ var: str
81
+
82
+ @dataclass(frozen=True, slots=True)
83
+ class WhileStmt(Stmt):
84
+ condition: str
85
+
86
+ @dataclass(frozen=True, slots=True)
87
+ class IfThenStmt(Stmt):
88
+ condition: str
89
+ then_clause: str
90
+ else_clause: str | None
91
+
92
+ @dataclass(frozen=True, slots=True)
93
+ class DoStmt(Stmt):
94
+ kind: str | None # WHILE or UNTIL or None
95
+ condition: str | None
96
+
97
+ @dataclass(frozen=True, slots=True)
98
+ class LoopStmt(Stmt):
99
+ kind: str | None
100
+ condition: str | None
101
+
102
+ @dataclass(frozen=True, slots=True)
103
+ class ExitStmt(Stmt):
104
+ target: str # FOR, WHILE, DO, SUB, FUNCTION
105
+
106
+ @dataclass(frozen=True, slots=True)
107
+ class OnGotoStmt(Stmt):
108
+ expr: str
109
+ targets: tuple[int, ...]
110
+
111
+ @dataclass(frozen=True, slots=True)
112
+ class OnGosubStmt(Stmt):
113
+ expr: str
114
+ targets: tuple[int, ...]
115
+
116
+ @dataclass(frozen=True, slots=True)
117
+ class SelectCaseStmt(Stmt):
118
+ expr: str
119
+
120
+ @dataclass(frozen=True, slots=True)
121
+ class CaseStmt(Stmt):
122
+ value: str
123
+
124
+ @dataclass(frozen=True, slots=True)
125
+ class CallStmt(Stmt):
126
+ name: str
127
+ args: str
128
+
129
+ @dataclass(frozen=True, slots=True)
130
+ class SubStmt(Stmt):
131
+ name: str
132
+ params: str
133
+
134
+ @dataclass(frozen=True, slots=True)
135
+ class FunctionStmt(Stmt):
136
+ name: str
137
+ params: str
138
+
139
+ @dataclass(frozen=True, slots=True)
140
+ class OnErrorStmt(Stmt):
141
+ target: int
142
+
143
+ @dataclass(frozen=True, slots=True)
144
+ class ResumeStmt(Stmt):
145
+ arg: str | None
146
+
147
+ @dataclass(frozen=True, slots=True)
148
+ class ErrorStmt(Stmt):
149
+ code: int
150
+
151
+ @dataclass(frozen=True, slots=True)
152
+ class AssertStmt(Stmt):
153
+ condition: str
154
+
155
+ @dataclass(frozen=True, slots=True)
156
+ class SwapStmt(Stmt):
157
+ a: str
158
+ b: str
159
+
160
+ @dataclass(frozen=True, slots=True)
161
+ class DefFnStmt(Stmt):
162
+ name: str
163
+ params: str
164
+ body: str
165
+
166
+ @dataclass(frozen=True, slots=True)
167
+ class OptionBaseStmt(Stmt):
168
+ base: int
169
+
170
+ @dataclass(frozen=True, slots=True)
171
+ class OnMeasureStmt(Stmt):
172
+ target: int
173
+
174
+ @dataclass(frozen=True, slots=True)
175
+ class OnTimerStmt(Stmt):
176
+ interval: str
177
+ target: int
178
+
179
+ @dataclass(frozen=True, slots=True)
180
+ class DataStmt(Stmt):
181
+ values: str
182
+
183
+ @dataclass(frozen=True, slots=True)
184
+ class ReadStmt(Stmt):
185
+ var_list: str
186
+
187
+ @dataclass(frozen=True, slots=True)
188
+ class LocalStmt(Stmt):
189
+ var_list: str
190
+
191
+ @dataclass(frozen=True, slots=True)
192
+ class StaticStmt(Stmt):
193
+ var_list: str
194
+
195
+ @dataclass(frozen=True, slots=True)
196
+ class SharedStmt(Stmt):
197
+ var_list: str
198
+
199
+
200
+ # ── Assignment ─────────────────────────────────────────────────────────
201
+
202
+ @dataclass(frozen=True, slots=True)
203
+ class LetStmt(Stmt):
204
+ name: str
205
+ expr: str
206
+
207
+ @dataclass(frozen=True, slots=True)
208
+ class LetArrayStmt(Stmt):
209
+ name: str
210
+ index_expr: str
211
+ value_expr: str
212
+
213
+ @dataclass(frozen=True, slots=True)
214
+ class LetStrStmt(Stmt):
215
+ name: str
216
+ expr: str
217
+
218
+
219
+ # ── I/O ────────────────────────────────────────────────────────────────
220
+
221
+ @dataclass(frozen=True, slots=True)
222
+ class PrintStmt(Stmt):
223
+ expr: str
224
+
225
+ @dataclass(frozen=True, slots=True)
226
+ class PrintUsingStmt(Stmt):
227
+ fmt: str
228
+ values: str
229
+
230
+ @dataclass(frozen=True, slots=True)
231
+ class InputStmt(Stmt):
232
+ prompt: str | None
233
+ var: str
234
+
235
+ @dataclass(frozen=True, slots=True)
236
+ class LineInputStmt(Stmt):
237
+ prompt: str | None
238
+ var: str
239
+
240
+ @dataclass(frozen=True, slots=True)
241
+ class GetStmt(Stmt):
242
+ var: str
243
+
244
+ @dataclass(frozen=True, slots=True)
245
+ class DimStmt(Stmt):
246
+ name: str
247
+ size: str
248
+
249
+ @dataclass(frozen=True, slots=True)
250
+ class RedimStmt(Stmt):
251
+ name: str
252
+ size: str
253
+
254
+ @dataclass(frozen=True, slots=True)
255
+ class EraseStmt(Stmt):
256
+ name: str
257
+
258
+ @dataclass(frozen=True, slots=True)
259
+ class PokeStmt(Stmt):
260
+ addr_expr: str
261
+ value_expr: str
262
+
263
+ @dataclass(frozen=True, slots=True)
264
+ class SysStmt(Stmt):
265
+ arg: str
266
+
267
+ @dataclass(frozen=True, slots=True)
268
+ class UnitaryStmt(Stmt):
269
+ name: str
270
+ matrix: str
271
+
272
+ @dataclass(frozen=True, slots=True)
273
+ class OpenStmt(Stmt):
274
+ path: str
275
+ mode: str
276
+ handle: int
277
+ encoding: str | None = None
278
+
279
+ @dataclass(frozen=True, slots=True)
280
+ class CloseStmt(Stmt):
281
+ handle: int
282
+
283
+ @dataclass(frozen=True, slots=True)
284
+ class PrintFileStmt(Stmt):
285
+ handle: int
286
+ data: str
287
+
288
+ @dataclass(frozen=True, slots=True)
289
+ class InputFileStmt(Stmt):
290
+ handle: int
291
+ var: str
292
+
293
+ @dataclass(frozen=True, slots=True)
294
+ class LprintStmt(Stmt):
295
+ expr: str
296
+
297
+ @dataclass(frozen=True, slots=True)
298
+ class ScreenStmt(Stmt):
299
+ mode: str
300
+
301
+ @dataclass(frozen=True, slots=True)
302
+ class ColorStmt(Stmt):
303
+ fg: str
304
+ bg: str | None
305
+
306
+ @dataclass(frozen=True, slots=True)
307
+ class LocateStmt(Stmt):
308
+ row: str
309
+ col: str
310
+
311
+ @dataclass(frozen=True, slots=True)
312
+ class ImportStmt(Stmt):
313
+ path: str
314
+
315
+ @dataclass(frozen=True, slots=True)
316
+ class ChainStmt(Stmt):
317
+ path: str
318
+
319
+ @dataclass(frozen=True, slots=True)
320
+ class MergeStmt(Stmt):
321
+ path: str
322
+
323
+
324
+ # ── Quantum ────────────────────────────────────────────────────────────
325
+
326
+ @dataclass(frozen=True, slots=True)
327
+ class MeasStmt(Stmt):
328
+ qubit_expr: str
329
+ var: str
330
+
331
+ @dataclass(frozen=True, slots=True)
332
+ class ResetStmt(Stmt):
333
+ qubit_expr: str
334
+
335
+ @dataclass(frozen=True, slots=True)
336
+ class MeasureBasisStmt(Stmt):
337
+ basis: str
338
+ qubit_expr: str
339
+
340
+ @dataclass(frozen=True, slots=True)
341
+ class SyndromeStmt(Stmt):
342
+ rest: str
343
+
344
+
345
+ # ── LOCC ───────────────────────────────────────────────────────────────
346
+
347
+ @dataclass(frozen=True, slots=True)
348
+ class SendStmt(Stmt):
349
+ reg: str
350
+ qubit_expr: str
351
+ var: str
352
+
353
+ @dataclass(frozen=True, slots=True)
354
+ class ShareStmt(Stmt):
355
+ reg1: str
356
+ q1: int
357
+ reg2: str
358
+ q2: int
359
+
360
+ @dataclass(frozen=True, slots=True)
361
+ class AtRegStmt(Stmt):
362
+ reg: str
363
+ inner: str
364
+
365
+
366
+ # ── Compound ───────────────────────────────────────────────────────────
367
+
368
+ @dataclass(frozen=True, slots=True)
369
+ class CompoundStmt(Stmt):
370
+ parts: tuple[str, ...]
371
+
372
+
373
+ # ── Gate application ───────────────────────────────────────────────────
374
+
375
+ @dataclass(frozen=True, slots=True)
376
+ class GateStmt(Stmt):
377
+ """Direct gate application: H 0, CX 0,1, RX PI/4, 0"""
378
+ name: str # canonical gate name (after alias resolution)
379
+ args: tuple[str, ...] # raw argument strings (params + qubits, unparsed)
380
+
381
+
382
+ # ── Fallback ───────────────────────────────────────────────────────────
383
+
384
+ @dataclass(frozen=True, slots=True)
385
+ class RawStmt(Stmt):
386
+ """Unrecognized statement — falls through to legacy regex path."""
387
+ pass
qbasic_core/strings.py ADDED
@@ -0,0 +1,107 @@
1
+ """QBASIC string support — string variables, functions, and operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from typing import Any
7
+
8
+
9
+ def _left(s: str, n: float) -> str:
10
+ return s[:int(n)]
11
+
12
+ def _right(s: str, n: float) -> str:
13
+ return s[-int(n):] if int(n) > 0 else ''
14
+
15
+ def _mid(s: str, start: float, length: float = -1) -> str:
16
+ i = int(start) - 1 # 1-based in BASIC
17
+ if length < 0:
18
+ return s[i:]
19
+ return s[i:i + int(length)]
20
+
21
+ def _instr(haystack: str, needle: str) -> float:
22
+ pos = haystack.find(needle)
23
+ return float(pos + 1) if pos >= 0 else 0.0 # 1-based, 0 = not found
24
+
25
+ def _asc(s: str) -> float:
26
+ return float(ord(s[0])) if s else 0.0
27
+
28
+ def _chr_fn(n: float) -> str:
29
+ return chr(int(n))
30
+
31
+ def _str_fn(n: float) -> str:
32
+ v = float(n)
33
+ return str(int(v)) if v == int(v) else str(v)
34
+
35
+ def _val_fn(s: str) -> float:
36
+ try:
37
+ return float(s)
38
+ except (ValueError, TypeError):
39
+ return 0.0
40
+
41
+ def _hex_fn(n: float) -> str:
42
+ return format(int(n), 'X')
43
+
44
+ def _bin_fn(n: float) -> str:
45
+ return format(int(n), 'b')
46
+
47
+ def _len_fn(x: Any) -> float:
48
+ if isinstance(x, str):
49
+ return float(len(x))
50
+ if isinstance(x, (list, tuple)):
51
+ return float(len(x))
52
+ return float(len(str(x)))
53
+
54
+
55
+ # Functions that return strings (names ending in $)
56
+ STRING_FUNCS: dict[str, Any] = {
57
+ 'LEFT$': _left, 'RIGHT$': _right, 'MID$': _mid,
58
+ 'CHR$': _chr_fn, 'STR$': _str_fn, 'HEX$': _hex_fn, 'BIN$': _bin_fn,
59
+ }
60
+
61
+ # Functions that take/return mixed types
62
+ MIXED_FUNCS: dict[str, Any] = {
63
+ 'ASC': _asc, 'VAL': _val_fn, 'INSTR': _instr, 'LEN': _len_fn,
64
+ }
65
+
66
+
67
+ class StringMixin:
68
+ """String variable and function support for QBasicTerminal.
69
+
70
+ Requires: TerminalProtocol — uses self.variables, self._safe_eval().
71
+ """
72
+
73
+ def _is_string_var(self, name: str) -> bool:
74
+ return name.endswith('$')
75
+
76
+ def _get_string_ns(self) -> dict[str, Any]:
77
+ """Return namespace entries for string functions."""
78
+ ns: dict[str, Any] = {}
79
+ ns.update(STRING_FUNCS)
80
+ ns.update(MIXED_FUNCS)
81
+ # String variables
82
+ for k, v in self.variables.items():
83
+ if isinstance(v, str):
84
+ ns[k] = v
85
+ return ns
86
+
87
+ def _eval_string_expr(self, expr: str) -> str | float:
88
+ """Evaluate an expression that might return a string."""
89
+ expr = expr.strip()
90
+ # Quoted string literal
91
+ if (expr.startswith('"') and expr.endswith('"')) or \
92
+ (expr.startswith("'") and expr.endswith("'")):
93
+ return expr[1:-1]
94
+ # String variable
95
+ if expr in self.variables and isinstance(self.variables[expr], str):
96
+ return self.variables[expr]
97
+ # Try numeric
98
+ try:
99
+ return self.eval_expr(expr)
100
+ except Exception:
101
+ return expr
102
+
103
+ def cmd_let_str(self, name: str, expr: str) -> None:
104
+ """Assign a string value to a string variable."""
105
+ val = self._eval_string_expr(expr)
106
+ self.variables[name] = val
107
+ self.io.writeln(f"{name} = {val!r}" if isinstance(val, str) else f"{name} = {val}")