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,186 @@
1
+ """Pre-compiled regex patterns for the QBASIC parser."""
2
+
3
+ import re
4
+
5
+ # ═══════════════════════════════════════════════════════════════════════
6
+ # Pre-compiled regexes
7
+ # ═══════════════════════════════════════════════════════════════════════
8
+
9
+ RE_LINE_NUM = re.compile(r'^(\d+)\s*(.*)')
10
+ RE_DEF_SINGLE = re.compile(r'(\w+)(?:\(([^)]*)\))?\s*=\s*(.*)')
11
+ RE_DEF_BEGIN = re.compile(r'DEF\s+BEGIN\s+(\w+)(?:\(([^)]*)\))?', re.IGNORECASE)
12
+ RE_REG_INDEX = re.compile(r'(\w+)\[(\d+)\]')
13
+ RE_AT_REG = re.compile(r'@([A-Z])\s+', re.IGNORECASE)
14
+ RE_AT_REG_LINE = re.compile(r'@([A-Z])\s+(.*)', re.IGNORECASE)
15
+ RE_SEND = re.compile(r'SEND\s+([A-Z])\s+(\S+)\s*->\s*(\w+)', re.IGNORECASE)
16
+ RE_SHARE = re.compile(r'SHARE\s+([A-Z])\s+(\d+)\s*,?\s*([A-Z])\s+(\d+)', re.IGNORECASE)
17
+ RE_MEAS = re.compile(r'MEAS\s+(\S+)\s*->\s*(\w+)', re.IGNORECASE)
18
+ RE_RESET = re.compile(r'RESET\s+(\S+)', re.IGNORECASE)
19
+ RE_UNITARY = re.compile(r'UNITARY\s+(\w+)\s*=\s*(\[.+\])', re.IGNORECASE)
20
+ RE_DIM = re.compile(r'DIM\s+(\w+)\((\d+)\)', re.IGNORECASE)
21
+ RE_REDIM = re.compile(r'REDIM\s+(\w+)\((\d+)\)', re.IGNORECASE)
22
+ RE_ERASE = re.compile(r'ERASE\s+(\w+)', re.IGNORECASE)
23
+ RE_GET = re.compile(r'GET\s+(\w+\$?)', re.IGNORECASE)
24
+ RE_INPUT = re.compile(r'INPUT\s+(?:"([^"]*)"\s*,\s*)?(\w+)', re.IGNORECASE)
25
+ RE_CTRL = re.compile(r'CTRL\s+(\w+)\s+(.*)', re.IGNORECASE)
26
+ RE_INV = re.compile(r'INV\s+(\w+)\s+(.*)', re.IGNORECASE)
27
+ RE_LET_ARRAY = re.compile(r'LET\s+(\w+)\((.+?)\)\s*=\s*(.*)', re.IGNORECASE)
28
+ RE_LET_VAR = re.compile(r'LET\s+(\w+)\s*=\s*(.*)', re.IGNORECASE)
29
+ RE_PRINT = re.compile(r'PRINT\s+(.*)', re.IGNORECASE)
30
+ RE_GOTO = re.compile(r'GOTO\s+(\d+)\s*$', re.IGNORECASE)
31
+ RE_GOSUB = re.compile(r'GOSUB\s+(\d+)\s*$', re.IGNORECASE)
32
+ RE_FOR = re.compile(
33
+ r'FOR\s+(\w+)\s*=\s*(.+?)\s+TO\s+(.+?)(?:\s+STEP\s+(.+))?\s*$', re.IGNORECASE)
34
+ RE_NEXT = re.compile(r'NEXT\s+(\w+)\s*$', re.IGNORECASE)
35
+ RE_WHILE = re.compile(r'WHILE\s+(.+)$', re.IGNORECASE)
36
+ RE_IF_THEN = re.compile(
37
+ r'IF\s+(.+?)\s+THEN\s+(.*?)(?:\s+ELSE\s+(.*))?$', re.IGNORECASE)
38
+ RE_GOTO_GOSUB_TARGET = re.compile(r'(GOTO|GOSUB)\s+(\d+)', re.IGNORECASE)
39
+ RE_MEASURE_BASIS = re.compile(
40
+ r'MEASURE_(X|Y|Z)\s+(\S+)', re.IGNORECASE)
41
+ RE_SYNDROME = re.compile(
42
+ r'SYNDROME\s+(.*)', re.IGNORECASE)
43
+
44
+ # ── Classic BASIC, memory, SUB/FUNCTION, debug ──────────────────────
45
+
46
+ RE_DATA = re.compile(r'DATA\s+(.*)', re.IGNORECASE)
47
+ RE_READ = re.compile(r'READ\s+(.*)', re.IGNORECASE)
48
+ RE_ON_GOTO = re.compile(r'ON\s+(.+?)\s+GOTO\s+([\d\s,]+)', re.IGNORECASE)
49
+ RE_ON_GOSUB = re.compile(r'ON\s+(.+?)\s+GOSUB\s+([\d\s,]+)', re.IGNORECASE)
50
+ RE_SELECT_CASE = re.compile(r'SELECT\s+CASE\s+(.*)', re.IGNORECASE)
51
+ RE_CASE = re.compile(r'CASE\s+(.*)', re.IGNORECASE)
52
+ RE_DO = re.compile(r'DO(?:\s+(WHILE|UNTIL)\s+(.+))?\s*$', re.IGNORECASE)
53
+ RE_LOOP_STMT = re.compile(r'LOOP(?:\s+(WHILE|UNTIL)\s+(.+))?\s*$', re.IGNORECASE)
54
+ RE_EXIT = re.compile(r'EXIT\s+(FOR|WHILE|DO|SUB|FUNCTION)\s*$', re.IGNORECASE)
55
+ RE_SUB = re.compile(r'SUB\s+(\w+)(?:\(([^)]*)\))?\s*$', re.IGNORECASE)
56
+ RE_END_SUB = re.compile(r'END\s+SUB\s*$', re.IGNORECASE)
57
+ RE_FUNCTION = re.compile(r'FUNCTION\s+(\w+)(?:\(([^)]*)\))?\s*$', re.IGNORECASE)
58
+ RE_END_FUNCTION = re.compile(r'END\s+FUNCTION\s*$', re.IGNORECASE)
59
+ RE_CALL = re.compile(r'CALL\s+(\w+)(?:\(([^)]*)\))?\s*$', re.IGNORECASE)
60
+ RE_LOCAL = re.compile(r'LOCAL\s+(.*)', re.IGNORECASE)
61
+ RE_STATIC_DECL = re.compile(r'STATIC\s+(.*)', re.IGNORECASE)
62
+ RE_SHARED = re.compile(r'SHARED\s+(.*)', re.IGNORECASE)
63
+ RE_ON_ERROR = re.compile(r'ON\s+ERROR\s+GOTO\s+(\d+)', re.IGNORECASE)
64
+ RE_RESUME = re.compile(r'RESUME(?:\s+(.+))?\s*$', re.IGNORECASE)
65
+ RE_ERROR_STMT = re.compile(r'ERROR\s+(\d+)', re.IGNORECASE)
66
+ RE_ASSERT = re.compile(r'ASSERT\s+(.*)', re.IGNORECASE)
67
+ RE_SWAP = re.compile(r'SWAP\s+(\w+\$?)\s*,\s*(\w+\$?)', re.IGNORECASE)
68
+ RE_POKE = re.compile(r'POKE\s+(.+?)\s*,\s*(.+)', re.IGNORECASE)
69
+ RE_SYS = re.compile(r'SYS\s+(.+)', re.IGNORECASE)
70
+ RE_OPEN = re.compile(
71
+ r'OPEN\s+"?([^"]+)"?\s+FOR\s+(INPUT|OUTPUT|APPEND|RANDOM)\s+AS\s+#?(\d+)'
72
+ r'(?:\s+ENCODING\s+"?([^"]*)"?)?',
73
+ re.IGNORECASE)
74
+ RE_CLOSE = re.compile(r'CLOSE\s+#?(\d+)', re.IGNORECASE)
75
+ RE_PRINT_FILE = re.compile(r'PRINT\s+#(\d+)\s*,\s*(.*)', re.IGNORECASE)
76
+ RE_INPUT_FILE = re.compile(r'INPUT\s+#(\d+)\s*,\s*(\w+\$?)', re.IGNORECASE)
77
+ RE_LINE_INPUT = re.compile(
78
+ r'LINE\s+INPUT\s+(?:"([^"]*)"\s*,\s*)?(\w+\$?)', re.IGNORECASE)
79
+ RE_OPTION_BASE = re.compile(r'OPTION\s+BASE\s+([01])', re.IGNORECASE)
80
+ RE_IMPORT = re.compile(r'IMPORT\s+"?([^"]+)"?', re.IGNORECASE)
81
+ RE_SAVE_EXPECT = re.compile(r'SAVE_EXPECT\s+(\w+)\s+([\d\s,]+)\s*->\s*(\w+)', re.IGNORECASE)
82
+ RE_SAVE_PROBS = re.compile(r'SAVE_PROBS\s+([\d\s,]+)\s*->\s*(\w+)', re.IGNORECASE)
83
+ RE_SAVE_AMPS = re.compile(r'SAVE_AMPS\s+([\d\s,]+)\s*->\s*(\w+)', re.IGNORECASE)
84
+ RE_SET_STATE = re.compile(r'SET_STATE\s+(.*)', re.IGNORECASE)
85
+ RE_TYPE_BEGIN = re.compile(r'TYPE\s+(\w+)', re.IGNORECASE)
86
+ RE_TYPE_FIELD = re.compile(r'(\w+)\s+AS\s+(INTEGER|FLOAT|STRING|QUBIT)', re.IGNORECASE)
87
+ RE_END_TYPE = re.compile(r'END\s+TYPE', re.IGNORECASE)
88
+ RE_DIM_TYPE = re.compile(r'DIM\s+(\w+)\s+AS\s+(\w+)', re.IGNORECASE)
89
+ RE_CHAIN = re.compile(r'CHAIN\s+"?([^"]+)"?', re.IGNORECASE)
90
+ RE_MERGE = re.compile(r'MERGE\s+"?([^"]+)"?', re.IGNORECASE)
91
+ RE_DEF_FN = re.compile(
92
+ r'DEF\s+FN\s*(\w+)\s*\(([^)]*)\)\s*=\s*(.*)', re.IGNORECASE)
93
+ RE_PRINT_USING = re.compile(
94
+ r'PRINT\s+USING\s+"([^"]+)"\s*;\s*(.*)', re.IGNORECASE)
95
+ RE_COLOR = re.compile(r'COLOR\s+(\w+)(?:\s*,\s*(\w+))?', re.IGNORECASE)
96
+ RE_LOCATE = re.compile(r'LOCATE\s+(\d+)\s*,\s*(\d+)', re.IGNORECASE)
97
+ RE_SCREEN = re.compile(r'SCREEN\s+(\d+)', re.IGNORECASE)
98
+ RE_LPRINT = re.compile(r'LPRINT\s+(.*)', re.IGNORECASE)
99
+ RE_ON_MEASURE = re.compile(r'ON\s+MEASURE\s+GOSUB\s+(\d+)', re.IGNORECASE)
100
+ RE_ON_TIMER = re.compile(r'ON\s+TIMER\s*\((\d+)\)\s+GOSUB\s+(\d+)', re.IGNORECASE)
101
+ RE_DIM_MULTI = re.compile(r'DIM\s+(\w+)\((\d+(?:\s*,\s*\d+)*)\)', re.IGNORECASE)
102
+ RE_LET_STR = re.compile(r'LET\s+(\w+\$)\s*=\s*(.*)', re.IGNORECASE)
103
+
104
+ __all__ = [
105
+ "RE_LINE_NUM",
106
+ "RE_DEF_SINGLE",
107
+ "RE_DEF_BEGIN",
108
+ "RE_REG_INDEX",
109
+ "RE_AT_REG",
110
+ "RE_AT_REG_LINE",
111
+ "RE_SEND",
112
+ "RE_SHARE",
113
+ "RE_MEAS",
114
+ "RE_RESET",
115
+ "RE_UNITARY",
116
+ "RE_DIM",
117
+ "RE_REDIM",
118
+ "RE_ERASE",
119
+ "RE_GET",
120
+ "RE_INPUT",
121
+ "RE_CTRL",
122
+ "RE_INV",
123
+ "RE_LET_ARRAY",
124
+ "RE_LET_VAR",
125
+ "RE_PRINT",
126
+ "RE_GOTO",
127
+ "RE_GOSUB",
128
+ "RE_FOR",
129
+ "RE_NEXT",
130
+ "RE_WHILE",
131
+ "RE_IF_THEN",
132
+ "RE_GOTO_GOSUB_TARGET",
133
+ "RE_MEASURE_BASIS",
134
+ "RE_SYNDROME",
135
+ "RE_DATA",
136
+ "RE_READ",
137
+ "RE_ON_GOTO",
138
+ "RE_ON_GOSUB",
139
+ "RE_SELECT_CASE",
140
+ "RE_CASE",
141
+ "RE_DO",
142
+ "RE_LOOP_STMT",
143
+ "RE_EXIT",
144
+ "RE_SUB",
145
+ "RE_END_SUB",
146
+ "RE_FUNCTION",
147
+ "RE_END_FUNCTION",
148
+ "RE_CALL",
149
+ "RE_LOCAL",
150
+ "RE_STATIC_DECL",
151
+ "RE_SHARED",
152
+ "RE_ON_ERROR",
153
+ "RE_RESUME",
154
+ "RE_ERROR_STMT",
155
+ "RE_ASSERT",
156
+ "RE_SWAP",
157
+ "RE_POKE",
158
+ "RE_SYS",
159
+ "RE_OPEN",
160
+ "RE_CLOSE",
161
+ "RE_PRINT_FILE",
162
+ "RE_INPUT_FILE",
163
+ "RE_LINE_INPUT",
164
+ "RE_OPTION_BASE",
165
+ "RE_IMPORT",
166
+ "RE_SAVE_EXPECT",
167
+ "RE_SAVE_PROBS",
168
+ "RE_SAVE_AMPS",
169
+ "RE_SET_STATE",
170
+ "RE_TYPE_BEGIN",
171
+ "RE_TYPE_FIELD",
172
+ "RE_END_TYPE",
173
+ "RE_DIM_TYPE",
174
+ "RE_CHAIN",
175
+ "RE_MERGE",
176
+ "RE_DEF_FN",
177
+ "RE_PRINT_USING",
178
+ "RE_COLOR",
179
+ "RE_LOCATE",
180
+ "RE_SCREEN",
181
+ "RE_LPRINT",
182
+ "RE_ON_MEASURE",
183
+ "RE_ON_TIMER",
184
+ "RE_DIM_MULTI",
185
+ "RE_LET_STR",
186
+ ]
@@ -0,0 +1,156 @@
1
+ """QBASIC profiler — profile mode, gate count, depth tracking, statistics accumulator."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import time
6
+
7
+ MAX_STATS_RUNS = 10000
8
+ import math
9
+ from typing import Any
10
+
11
+
12
+ class _NullIOPort:
13
+ def write(self, text): pass
14
+ def writeln(self, text): pass
15
+ def read_line(self, prompt): return ''
16
+
17
+
18
+ class ProfilerMixin:
19
+ """Profiling and statistics for QBasicTerminal.
20
+
21
+ Requires: TerminalProtocol — uses self.program, self.last_counts,
22
+ self.shots, self.cmd_run().
23
+ """
24
+
25
+ def _init_profiler(self) -> None:
26
+ self._profile_mode: bool = False
27
+ self._profile_data: dict[int, dict[str, float]] = {}
28
+ self._profile_start: float = 0.0
29
+ self._depth_counter: int = 0
30
+ self._gate_counter: int = 0
31
+ self._stats_runs: list[dict[str, int]] = []
32
+
33
+ # ── Profile mode ───────────────────────────────────────────────────
34
+
35
+ def cmd_profile(self, rest: str = '') -> None:
36
+ """PROFILE [ON|OFF|SHOW] — toggle profiling or show results."""
37
+ arg = rest.strip().upper()
38
+ if arg == 'ON':
39
+ self._profile_mode = True
40
+ self._profile_data.clear()
41
+ self.io.writeln("PROFILE ON")
42
+ elif arg == 'OFF':
43
+ self._profile_mode = False
44
+ self.io.writeln("PROFILE OFF")
45
+ elif arg == 'SHOW' or not arg:
46
+ self._show_profile()
47
+ else:
48
+ self.io.writeln("?USAGE: PROFILE [ON|OFF|SHOW]")
49
+
50
+ def _profile_line_start(self, line_num: int) -> None:
51
+ if self._profile_mode:
52
+ self._profile_start = time.perf_counter()
53
+
54
+ def _profile_line_end(self, line_num: int, gates: int = 0) -> None:
55
+ if self._profile_mode:
56
+ dt = (time.perf_counter() - self._profile_start) * 1000 # ms
57
+ if line_num not in self._profile_data:
58
+ self._profile_data[line_num] = {'time_ms': 0.0, 'calls': 0, 'gates': 0}
59
+ entry = self._profile_data[line_num]
60
+ entry['time_ms'] += dt
61
+ entry['calls'] += 1
62
+ entry['gates'] += gates
63
+
64
+ def _show_profile(self) -> None:
65
+ if not self._profile_data:
66
+ self.io.writeln(" No profile data (PROFILE ON, then RUN)")
67
+ return
68
+ self.io.writeln("\n Profile Results:")
69
+ self.io.writeln(f" {'Line':>6} {'Time(ms)':>10} {'Calls':>6} {'Gates':>6} Source")
70
+ total_time = sum(d['time_ms'] for d in self._profile_data.values())
71
+ for ln in sorted(self._profile_data.keys()):
72
+ d = self._profile_data[ln]
73
+ src = self.program.get(ln, '')[:40]
74
+ pct = 100 * d['time_ms'] / total_time if total_time > 0 else 0
75
+ self.io.writeln(f" {ln:>6} {d['time_ms']:>9.2f} {d['calls']:>6} {d['gates']:>6} {src}")
76
+ self.io.writeln(f"\n Total: {total_time:.2f} ms")
77
+ self.io.writeln('')
78
+
79
+ # ── Gate/depth tracking ────────────────────────────────────────────
80
+
81
+ def _track_gate(self) -> None:
82
+ self._gate_counter += 1
83
+
84
+ def _track_depth(self, depth: int) -> None:
85
+ self._depth_counter = max(self._depth_counter, depth)
86
+
87
+ # ── Statistics accumulator ─────────────────────────────────────────
88
+
89
+ def cmd_stats(self, rest: str = '') -> None:
90
+ """STATS [N|SHOW|CLEAR] — multi-run statistics accumulator."""
91
+ arg = rest.strip().upper()
92
+ if arg == 'CLEAR':
93
+ self._stats_runs.clear()
94
+ self.io.writeln("STATS CLEARED")
95
+ return
96
+ if arg == 'SHOW' or not arg:
97
+ self._show_stats()
98
+ return
99
+ # STATS N — run N trials
100
+ try:
101
+ n = int(arg)
102
+ except ValueError:
103
+ self.io.writeln("?USAGE: STATS [N|SHOW|CLEAR]")
104
+ return
105
+ if n < 1:
106
+ self.io.writeln("?STATS needs at least 1 run")
107
+ return
108
+ self.io.writeln(f"\nRunning {n} trials...")
109
+ for trial in range(n):
110
+ old_io = self.io
111
+ self.io = _NullIOPort()
112
+ try:
113
+ self.cmd_run()
114
+ finally:
115
+ self.io = old_io
116
+ if self.last_counts:
117
+ if len(self._stats_runs) >= MAX_STATS_RUNS:
118
+ self.io.writeln(f"?STATS: run limit ({MAX_STATS_RUNS}) reached, stopping collection")
119
+ break
120
+ self._stats_runs.append(dict(self.last_counts))
121
+ if n > 10 and (trial + 1) % (n // 10) == 0:
122
+ self.io.write(f" {100 * (trial + 1) // n}%..." + '\r')
123
+ if n > 10:
124
+ self.io.write(" " * 30 + '\r')
125
+ self.io.writeln(f"Collected {len(self._stats_runs)} runs ({n} trials)")
126
+
127
+ def _show_stats(self) -> None:
128
+ if not self._stats_runs:
129
+ self.io.writeln(" No statistics collected (STATS N to run N trials)")
130
+ return
131
+ n = len(self._stats_runs)
132
+ # Aggregate: count how often each state appears across runs
133
+ state_totals: dict[str, list[int]] = {}
134
+ for run in self._stats_runs:
135
+ total = sum(run.values())
136
+ for state, count in run.items():
137
+ if state not in state_totals:
138
+ state_totals[state] = []
139
+ state_totals[state].append(count)
140
+ # States not seen in this run get 0
141
+ for state in state_totals:
142
+ if state not in run:
143
+ state_totals[state].append(0)
144
+ # Pad lists to same length
145
+ for state in state_totals:
146
+ while len(state_totals[state]) < n:
147
+ state_totals[state].append(0)
148
+ self.io.writeln(f"\n Statistics over {n} runs:")
149
+ self.io.writeln(f" {'State':>10} {'Mean':>8} {'StdDev':>8} {'Min':>6} {'Max':>6}")
150
+ for state in sorted(state_totals.keys()):
151
+ vals = state_totals[state]
152
+ mean = sum(vals) / len(vals)
153
+ variance = sum((v - mean) ** 2 for v in vals) / len(vals)
154
+ std = math.sqrt(variance)
155
+ self.io.writeln(f" |{state}\u27E9 {mean:>8.1f} {std:>8.2f} {min(vals):>6} {max(vals):>6}")
156
+ self.io.writeln('')