qubasic 0.6.0__tar.gz → 0.6.3__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.6.0 → qubasic-0.6.3}/CHANGELOG.md +22 -1
- {qubasic-0.6.0/qubasic.egg-info → qubasic-0.6.3}/PKG-INFO +12 -8
- {qubasic-0.6.0 → qubasic-0.6.3}/README.md +11 -7
- {qubasic-0.6.0 → qubasic-0.6.3}/pyproject.toml +16 -7
- {qubasic-0.6.0 → qubasic-0.6.3/qubasic.egg-info}/PKG-INFO +12 -8
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic.egg-info/SOURCES.txt +4 -2
- qubasic-0.6.3/qubasic.egg-info/entry_points.txt +2 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic.egg-info/top_level.txt +0 -1
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/__init__.py +1 -1
- qubasic-0.6.3/qubasic_core/__main__.py +6 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/analysis.py +1 -1
- qubasic-0.6.0/qubasic.py → qubasic-0.6.3/qubasic_core/cli.py +4 -3
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/control_flow.py +77 -37
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/engine.py +2 -1
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/engine_state.py +15 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/exec_context.py +1 -1
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/executor.py +47 -19
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/expression.py +1 -1
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/file_io.py +2 -1
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/gates.py +2 -2
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/locc_engine.py +3 -3
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/locc_execution.py +5 -5
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/memory.py +8 -6
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/parser.py +8 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/qol.py +7 -15
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/terminal.py +30 -12
- qubasic-0.6.3/tests/test_features.py +2690 -0
- qubasic-0.6.3/tests/test_qubasic.py +1524 -0
- qubasic-0.6.0/qubasic.egg-info/entry_points.txt +0 -2
- qubasic-0.6.0/qubasic_core/__main__.py +0 -6
- {qubasic-0.6.0 → qubasic-0.6.3}/LICENSE +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/MANIFEST.in +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/examples/bell.qb +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/examples/grover3.qb +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/examples/locc_teleport.qb +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/examples/sweep_rx.qb +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic.egg-info/dependency_links.txt +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic.egg-info/requires.txt +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/backend.py +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/classic.py +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/debug.py +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/demos.py +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/display.py +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/errors.py +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/help_text.py +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/io_protocol.py +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/locc.py +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/locc_commands.py +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/locc_display.py +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/mock_backend.py +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/noise_mixin.py +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/patterns.py +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/profiler.py +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/program_mgmt.py +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/protocol.py +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/scope.py +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/screen.py +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/state_display.py +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/statements.py +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/strings.py +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/subs.py +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/qubasic_core/sweep.py +0 -0
- {qubasic-0.6.0 → qubasic-0.6.3}/setup.cfg +0 -0
|
@@ -1,8 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.3 (2026-06-18)
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- Colon-compound `@register` lines in LOCC mode now apply every gate. `@A H 0 : @A CX 0,1` (and the inherited form `@A H 0 : CX 0,1`) were captured as a single statement and mis-tokenized, so all but the first gate were silently dropped. The parser now splits them into per-statement parts with register inheritance, and the immediate-mode REPL path does the same.
|
|
7
|
+
- `SAVE_EXPECT`, `SAVE_PROBS`, and `SAVE_AMPS` no longer overwrite their target variable with 0 at circuit-build time. The placeholder keeps any prior value, so a program that re-runs with the SAVE line still present can read the previous result during the build pass (the post-run extraction then fills in the fresh value).
|
|
8
|
+
- `PRINT` honors `;` and `,` between multiple items: `PRINT "S ="; x` concatenates and `PRINT a, b` advances to the next 14-column zone. Separators inside quoted strings and inside call parentheses (`PRINT LEFT$(s$, 3)`) are no longer treated as item breaks.
|
|
9
|
+
- `PRINT` variable substitution no longer rewrites identifiers inside quoted string literals, so a label such as `PRINT "value of S"` is emitted verbatim even when `S` is a defined variable.
|
|
10
|
+
|
|
11
|
+
## 0.6.2 (2026-06-18)
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- Changing the qubit count after a run (via `QUBITS n` or `POKE $D000`) now invalidates the cached statevector, so `MAP`, a `PEEK` of the qubit-state block, and `BLOCH` no longer reshape a stale statevector to the new qubit count and crash.
|
|
15
|
+
|
|
16
|
+
## 0.6.1 (2026-06-18)
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- Changing noise strength between runs (for example `NOISE amplitude_damping 0.1` then `0.9`) now rebuilds the circuit instead of reusing the first noise level, so sequential noise comparisons are correct.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- The CLI now lives in `qubasic_core/cli.py`; the `qubasic` console script and `python -m qubasic_core` are unchanged, but the former top-level `qubasic` module is removed. Test suites moved under `tests/`.
|
|
23
|
+
|
|
3
24
|
## 0.6.0 (2026-06-18)
|
|
4
25
|
|
|
5
|
-
Correctness, scalability, and usability
|
|
26
|
+
Correctness, scalability, and usability improvements.
|
|
6
27
|
|
|
7
28
|
### Fixed
|
|
8
29
|
- **Time-travel debugging now works**: STEP records statevector checkpoints, so REWIND/FORWARD/HISTORY operate (previously `_checkpoint_sv` was never called).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qubasic
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.3
|
|
4
4
|
Summary: Quantum BASIC Interactive Terminal
|
|
5
5
|
Author-email: "Charles C. Norton" <machineelv@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -73,11 +73,12 @@ Requires Python >= 3.10, Qiskit >= 1.0, qiskit-aer >= 0.13.
|
|
|
73
73
|
## Usage
|
|
74
74
|
|
|
75
75
|
```
|
|
76
|
-
|
|
77
|
-
python
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
qubasic Interactive REPL (installed console script)
|
|
77
|
+
python -m qubasic_core Same, without installing
|
|
78
|
+
qubasic script.qb Run a script file
|
|
79
|
+
qubasic --quiet script Suppress banner, output results only
|
|
80
|
+
qubasic --json script Machine-readable JSON output
|
|
81
|
+
qubasic --help Show CLI help
|
|
81
82
|
```
|
|
82
83
|
|
|
83
84
|
Headless (no REPL):
|
|
@@ -685,7 +686,7 @@ Programs with SEND use prefix/suffix splitting: the deterministic prefix (before
|
|
|
685
686
|
## JSON output
|
|
686
687
|
|
|
687
688
|
```
|
|
688
|
-
|
|
689
|
+
qubasic --json examples/bell.qb
|
|
689
690
|
```
|
|
690
691
|
|
|
691
692
|
```json
|
|
@@ -699,8 +700,9 @@ python qubasic.py --json examples/bell.qb
|
|
|
699
700
|
## Architecture
|
|
700
701
|
|
|
701
702
|
```
|
|
702
|
-
qubasic.py CLI entry point
|
|
703
703
|
qubasic_core/
|
|
704
|
+
cli.py CLI entry point (qubasic / python -m qubasic_core)
|
|
705
|
+
__main__.py Module entry point
|
|
704
706
|
engine_state.py Engine: standalone state container
|
|
705
707
|
terminal.py QBasicTerminal: REPL + command dispatch
|
|
706
708
|
engine.py Constants, gate tables, numpy simulation, LOCCEngine
|
|
@@ -729,6 +731,8 @@ qubasic_core/
|
|
|
729
731
|
demos.py Built-in demo circuits
|
|
730
732
|
protocol.py TerminalProtocol (mixin contract)
|
|
731
733
|
mock_backend.py MockAerSimulator for fast testing
|
|
734
|
+
tests/ Test suites (test_qubasic.py, test_features.py)
|
|
735
|
+
examples/ Sample .qb programs
|
|
732
736
|
```
|
|
733
737
|
|
|
734
738
|
`Engine` holds all program state. `QBasicTerminal` inherits `Engine` + 20 mixins. Execution methods live on `QBasicTerminal`, so headless/agent use should instantiate `QBasicTerminal` (the `Engine` base is a state container only).
|
|
@@ -40,11 +40,12 @@ Requires Python >= 3.10, Qiskit >= 1.0, qiskit-aer >= 0.13.
|
|
|
40
40
|
## Usage
|
|
41
41
|
|
|
42
42
|
```
|
|
43
|
-
|
|
44
|
-
python
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
qubasic Interactive REPL (installed console script)
|
|
44
|
+
python -m qubasic_core Same, without installing
|
|
45
|
+
qubasic script.qb Run a script file
|
|
46
|
+
qubasic --quiet script Suppress banner, output results only
|
|
47
|
+
qubasic --json script Machine-readable JSON output
|
|
48
|
+
qubasic --help Show CLI help
|
|
48
49
|
```
|
|
49
50
|
|
|
50
51
|
Headless (no REPL):
|
|
@@ -652,7 +653,7 @@ Programs with SEND use prefix/suffix splitting: the deterministic prefix (before
|
|
|
652
653
|
## JSON output
|
|
653
654
|
|
|
654
655
|
```
|
|
655
|
-
|
|
656
|
+
qubasic --json examples/bell.qb
|
|
656
657
|
```
|
|
657
658
|
|
|
658
659
|
```json
|
|
@@ -666,8 +667,9 @@ python qubasic.py --json examples/bell.qb
|
|
|
666
667
|
## Architecture
|
|
667
668
|
|
|
668
669
|
```
|
|
669
|
-
qubasic.py CLI entry point
|
|
670
670
|
qubasic_core/
|
|
671
|
+
cli.py CLI entry point (qubasic / python -m qubasic_core)
|
|
672
|
+
__main__.py Module entry point
|
|
671
673
|
engine_state.py Engine: standalone state container
|
|
672
674
|
terminal.py QBasicTerminal: REPL + command dispatch
|
|
673
675
|
engine.py Constants, gate tables, numpy simulation, LOCCEngine
|
|
@@ -696,6 +698,8 @@ qubasic_core/
|
|
|
696
698
|
demos.py Built-in demo circuits
|
|
697
699
|
protocol.py TerminalProtocol (mixin contract)
|
|
698
700
|
mock_backend.py MockAerSimulator for fast testing
|
|
701
|
+
tests/ Test suites (test_qubasic.py, test_features.py)
|
|
702
|
+
examples/ Sample .qb programs
|
|
699
703
|
```
|
|
700
704
|
|
|
701
705
|
`Engine` holds all program state. `QBasicTerminal` inherits `Engine` + 20 mixins. Execution methods live on `QBasicTerminal`, so headless/agent use should instantiate `QBasicTerminal` (the `Engine` base is a state container only).
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "qubasic"
|
|
7
|
-
version = "0.6.
|
|
7
|
+
version = "0.6.3"
|
|
8
8
|
description = "Quantum BASIC Interactive Terminal"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -42,10 +42,9 @@ charts = ["plotille>=5.0"]
|
|
|
42
42
|
readline = ["pyreadline3>=3.4; sys_platform == 'win32'"]
|
|
43
43
|
|
|
44
44
|
[project.scripts]
|
|
45
|
-
qubasic = "
|
|
45
|
+
qubasic = "qubasic_core.cli:main"
|
|
46
46
|
|
|
47
47
|
[tool.setuptools]
|
|
48
|
-
py-modules = ["qubasic"]
|
|
49
48
|
packages = ["qubasic_core"]
|
|
50
49
|
|
|
51
50
|
[tool.setuptools.package-data]
|
|
@@ -66,9 +65,19 @@ skip_empty = true
|
|
|
66
65
|
|
|
67
66
|
[tool.mypy]
|
|
68
67
|
python_version = "3.10"
|
|
69
|
-
|
|
68
|
+
ignore_missing_imports = true
|
|
70
69
|
warn_unused_configs = true
|
|
71
|
-
disallow_untyped_defs = true
|
|
72
|
-
check_untyped_defs = true
|
|
73
70
|
warn_redundant_casts = true
|
|
74
|
-
|
|
71
|
+
# QBasicTerminal composes 20 mixins; each mixin accesses attributes the host
|
|
72
|
+
# class provides, which mypy cannot resolve in isolation. The codebase is
|
|
73
|
+
# intentionally gradually typed, so the strict per-method settings and the
|
|
74
|
+
# cross-mixin attribute check are disabled while keeping mypy useful for real
|
|
75
|
+
# type mismatches, undefined names, and signature checks.
|
|
76
|
+
disallow_untyped_defs = false
|
|
77
|
+
check_untyped_defs = false
|
|
78
|
+
warn_return_any = false
|
|
79
|
+
warn_unused_ignores = false
|
|
80
|
+
# attr-defined: cross-mixin attributes the host class provides.
|
|
81
|
+
# has-type: types behind the lazy __getattr__ re-exports cannot be inferred.
|
|
82
|
+
# misc/union-attr: mixin base-class attribute overlap and optional access.
|
|
83
|
+
disable_error_code = ["attr-defined", "has-type", "misc", "union-attr"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qubasic
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.3
|
|
4
4
|
Summary: Quantum BASIC Interactive Terminal
|
|
5
5
|
Author-email: "Charles C. Norton" <machineelv@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -73,11 +73,12 @@ Requires Python >= 3.10, Qiskit >= 1.0, qiskit-aer >= 0.13.
|
|
|
73
73
|
## Usage
|
|
74
74
|
|
|
75
75
|
```
|
|
76
|
-
|
|
77
|
-
python
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
qubasic Interactive REPL (installed console script)
|
|
77
|
+
python -m qubasic_core Same, without installing
|
|
78
|
+
qubasic script.qb Run a script file
|
|
79
|
+
qubasic --quiet script Suppress banner, output results only
|
|
80
|
+
qubasic --json script Machine-readable JSON output
|
|
81
|
+
qubasic --help Show CLI help
|
|
81
82
|
```
|
|
82
83
|
|
|
83
84
|
Headless (no REPL):
|
|
@@ -685,7 +686,7 @@ Programs with SEND use prefix/suffix splitting: the deterministic prefix (before
|
|
|
685
686
|
## JSON output
|
|
686
687
|
|
|
687
688
|
```
|
|
688
|
-
|
|
689
|
+
qubasic --json examples/bell.qb
|
|
689
690
|
```
|
|
690
691
|
|
|
691
692
|
```json
|
|
@@ -699,8 +700,9 @@ python qubasic.py --json examples/bell.qb
|
|
|
699
700
|
## Architecture
|
|
700
701
|
|
|
701
702
|
```
|
|
702
|
-
qubasic.py CLI entry point
|
|
703
703
|
qubasic_core/
|
|
704
|
+
cli.py CLI entry point (qubasic / python -m qubasic_core)
|
|
705
|
+
__main__.py Module entry point
|
|
704
706
|
engine_state.py Engine: standalone state container
|
|
705
707
|
terminal.py QBasicTerminal: REPL + command dispatch
|
|
706
708
|
engine.py Constants, gate tables, numpy simulation, LOCCEngine
|
|
@@ -729,6 +731,8 @@ qubasic_core/
|
|
|
729
731
|
demos.py Built-in demo circuits
|
|
730
732
|
protocol.py TerminalProtocol (mixin contract)
|
|
731
733
|
mock_backend.py MockAerSimulator for fast testing
|
|
734
|
+
tests/ Test suites (test_qubasic.py, test_features.py)
|
|
735
|
+
examples/ Sample .qb programs
|
|
732
736
|
```
|
|
733
737
|
|
|
734
738
|
`Engine` holds all program state. `QBasicTerminal` inherits `Engine` + 20 mixins. Execution methods live on `QBasicTerminal`, so headless/agent use should instantiate `QBasicTerminal` (the `Engine` base is a state container only).
|
|
@@ -3,7 +3,6 @@ LICENSE
|
|
|
3
3
|
MANIFEST.in
|
|
4
4
|
README.md
|
|
5
5
|
pyproject.toml
|
|
6
|
-
qubasic.py
|
|
7
6
|
examples/bell.qb
|
|
8
7
|
examples/grover3.qb
|
|
9
8
|
examples/locc_teleport.qb
|
|
@@ -19,6 +18,7 @@ qubasic_core/__main__.py
|
|
|
19
18
|
qubasic_core/analysis.py
|
|
20
19
|
qubasic_core/backend.py
|
|
21
20
|
qubasic_core/classic.py
|
|
21
|
+
qubasic_core/cli.py
|
|
22
22
|
qubasic_core/control_flow.py
|
|
23
23
|
qubasic_core/debug.py
|
|
24
24
|
qubasic_core/demos.py
|
|
@@ -54,4 +54,6 @@ qubasic_core/statements.py
|
|
|
54
54
|
qubasic_core/strings.py
|
|
55
55
|
qubasic_core/subs.py
|
|
56
56
|
qubasic_core/sweep.py
|
|
57
|
-
qubasic_core/terminal.py
|
|
57
|
+
qubasic_core/terminal.py
|
|
58
|
+
tests/test_features.py
|
|
59
|
+
tests/test_qubasic.py
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
|
-
QUBASIC —
|
|
3
|
+
QUBASIC — command-line entry point.
|
|
4
4
|
|
|
5
5
|
Usage:
|
|
6
|
-
|
|
7
|
-
python
|
|
6
|
+
qubasic Interactive REPL (installed console script)
|
|
7
|
+
python -m qubasic_core Same, run as a module
|
|
8
|
+
qubasic script.qb Run a script file
|
|
8
9
|
"""
|
|
9
10
|
|
|
10
11
|
import sys
|
|
@@ -63,46 +63,86 @@ class ControlFlowMixin:
|
|
|
63
63
|
self.variables[name] = val
|
|
64
64
|
return True, ExecResult.ADVANCE
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
66
|
+
@staticmethod
|
|
67
|
+
def _split_print_items(expr: str) -> list[tuple[str, str]]:
|
|
68
|
+
"""Split a PRINT argument list into (item, trailing-separator) pairs.
|
|
69
|
+
|
|
70
|
+
';' and ',' are recognized only at top level (outside quotes and
|
|
71
|
+
parentheses/brackets), so PRINT LEFT$(s$, 3) stays one item and a comma
|
|
72
|
+
inside a quoted literal is preserved. The separator recorded with each
|
|
73
|
+
item is the one that follows it; '' marks the final item / no trailing
|
|
74
|
+
separator.
|
|
75
|
+
"""
|
|
76
|
+
items: list[tuple[str, str]] = []
|
|
77
|
+
buf = ''
|
|
78
|
+
depth = 0
|
|
79
|
+
quote: str | None = None
|
|
80
|
+
for ch in expr:
|
|
81
|
+
if quote:
|
|
82
|
+
buf += ch
|
|
83
|
+
if ch == quote:
|
|
84
|
+
quote = None
|
|
85
|
+
elif ch in ('"', "'"):
|
|
86
|
+
quote = ch
|
|
87
|
+
buf += ch
|
|
88
|
+
elif ch in '([':
|
|
89
|
+
depth += 1
|
|
90
|
+
buf += ch
|
|
91
|
+
elif ch in ')]':
|
|
92
|
+
depth = max(0, depth - 1)
|
|
93
|
+
buf += ch
|
|
94
|
+
elif ch in ';,' and depth == 0:
|
|
95
|
+
items.append((buf.strip(), ch))
|
|
96
|
+
buf = ''
|
|
97
|
+
else:
|
|
98
|
+
buf += ch
|
|
99
|
+
if buf.strip():
|
|
100
|
+
items.append((buf.strip(), ''))
|
|
101
|
+
return items
|
|
102
|
+
|
|
103
|
+
def _eval_print_item(self, item: str, run_vars: dict[str, Any]) -> str:
|
|
104
|
+
"""Evaluate a single PRINT item to its display string."""
|
|
105
|
+
item = item.strip()
|
|
106
|
+
if not item:
|
|
107
|
+
return ''
|
|
108
|
+
# Quoted literal: emit verbatim (no substitution, no SPC/TAB).
|
|
109
|
+
if (item[0] == '"' and item[-1] == '"') or (item[0] == "'" and item[-1] == "'"):
|
|
110
|
+
return item[1:-1]
|
|
111
|
+
text = self._substitute_vars(item, run_vars)
|
|
112
|
+
|
|
113
|
+
def _spaces(m):
|
|
91
114
|
try:
|
|
92
|
-
|
|
93
|
-
result = self._safe_eval(text, extra_ns=ns)
|
|
94
|
-
output = str(result)
|
|
115
|
+
return ' ' * max(0, int(self._eval_with_vars(m.group(1), run_vars)))
|
|
95
116
|
except Exception:
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
117
|
+
return ''
|
|
118
|
+
text = re.sub(r'\bSPC\s*\(([^)]+)\)', _spaces, text, flags=re.IGNORECASE)
|
|
119
|
+
text = re.sub(r'\bTAB\s*\(([^)]+)\)', _spaces, text, flags=re.IGNORECASE)
|
|
120
|
+
if not text.strip():
|
|
121
|
+
return text # standalone SPC/TAB -> whitespace
|
|
122
|
+
try:
|
|
123
|
+
ns = run_vars.as_dict() if hasattr(run_vars, 'as_dict') else (
|
|
124
|
+
run_vars if isinstance(run_vars, dict) else dict(run_vars))
|
|
125
|
+
return str(self._safe_eval(text, extra_ns=ns))
|
|
126
|
+
except Exception:
|
|
127
|
+
return text
|
|
128
|
+
|
|
129
|
+
def _cf_print(self, stmt: str, run_vars: dict[str, Any],
|
|
130
|
+
parsed: PrintStmt) -> tuple[bool, ExecOutcome]:
|
|
131
|
+
items = self._split_print_items(parsed.expr)
|
|
132
|
+
if not items:
|
|
133
|
+
self.io.writeln('') # bare PRINT -> blank line
|
|
134
|
+
return True, ExecResult.ADVANCE
|
|
135
|
+
out = ''
|
|
136
|
+
for item, sep in items:
|
|
137
|
+
out += self._eval_print_item(item, run_vars)
|
|
138
|
+
if sep == ',': # advance to next 14-column zone
|
|
139
|
+
col = len(out) % 14
|
|
140
|
+
out += ' ' * (14 - col if col else 14)
|
|
141
|
+
# A trailing ';' or ',' suppresses the newline (cursor stays on line).
|
|
142
|
+
if items[-1][1] in (';', ','):
|
|
143
|
+
self.io.write(out)
|
|
104
144
|
else:
|
|
105
|
-
self.io.writeln(
|
|
145
|
+
self.io.writeln(out)
|
|
106
146
|
return True, ExecResult.ADVANCE
|
|
107
147
|
|
|
108
148
|
def _cf_goto(self, stmt: str, sorted_lines: list[int],
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from enum import Enum, auto
|
|
6
|
+
from typing import Any
|
|
6
7
|
import numpy as np
|
|
7
8
|
from qubasic_core.patterns import (
|
|
8
9
|
RE_LINE_NUM, RE_DEF_SINGLE, RE_DEF_BEGIN, RE_REG_INDEX,
|
|
@@ -79,7 +80,7 @@ try:
|
|
|
79
80
|
from rich.panel import Panel as _RichPanel
|
|
80
81
|
from rich.text import Text as _RichText
|
|
81
82
|
_RICH = True
|
|
82
|
-
_console = _RichConsole(highlight=False)
|
|
83
|
+
_console: Any = _RichConsole(highlight=False)
|
|
83
84
|
except ImportError:
|
|
84
85
|
_RICH = False
|
|
85
86
|
_console = None
|
|
@@ -71,6 +71,7 @@ class Engine:
|
|
|
71
71
|
self._circuit_cache_key: Any = None
|
|
72
72
|
self._circuit_cache: Any = None
|
|
73
73
|
self._pending_set_state: Any = None # immediate SET_STATE for next RUN
|
|
74
|
+
self._poke_state_prep: dict[Any, Any] = {} # qubit prep from POKE $0100
|
|
74
75
|
|
|
75
76
|
# LOCC
|
|
76
77
|
self.locc: Any = None
|
|
@@ -118,3 +119,17 @@ class Engine:
|
|
|
118
119
|
self._circuit_cache = None
|
|
119
120
|
self._pending_set_state = None
|
|
120
121
|
self._poke_state_prep = {}
|
|
122
|
+
|
|
123
|
+
def _invalidate_run_state(self) -> None:
|
|
124
|
+
"""Drop cached results that describe a now-stale configuration.
|
|
125
|
+
|
|
126
|
+
Called when the qubit count changes: last_sv, last_counts,
|
|
127
|
+
last_circuit, and the transpile cache all assume the previous
|
|
128
|
+
num_qubits, so reading them afterwards would mislabel (or, for
|
|
129
|
+
the reshape in _peek_qubit/_bloch_vector, crash) the state.
|
|
130
|
+
"""
|
|
131
|
+
self.last_counts = None
|
|
132
|
+
self.last_sv = None
|
|
133
|
+
self.last_circuit = None
|
|
134
|
+
self._circuit_cache_key = None
|
|
135
|
+
self._circuit_cache = None
|
|
@@ -12,7 +12,7 @@ class ExecContext:
|
|
|
12
12
|
|
|
13
13
|
sorted_lines: list[int]
|
|
14
14
|
ip: int
|
|
15
|
-
run_vars: dict[str, Any]
|
|
15
|
+
run_vars: Any # dict[str, Any] or a Scope wrapper
|
|
16
16
|
loop_stack: list[dict[str, Any]] = field(default_factory=list)
|
|
17
17
|
iteration_count: int = 0
|
|
18
18
|
max_iterations: int = 100_000
|
|
@@ -328,20 +328,42 @@ class ExecutorMixin:
|
|
|
328
328
|
set(self.subroutines.keys()) |
|
|
329
329
|
set(self._custom_gates.keys())
|
|
330
330
|
)
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
# Don't expand record bases (dicts) — leave p in p.x intact so
|
|
340
|
-
# the expression evaluator can resolve the field.
|
|
341
|
-
if isinstance(merged[tok], dict):
|
|
331
|
+
|
|
332
|
+
def _sub_segment(seg: str) -> str:
|
|
333
|
+
# Tokenize: split on word boundaries, preserving delimiters
|
|
334
|
+
tokens = re.split(r'(\b\w+\b)', seg)
|
|
335
|
+
for i, tok in enumerate(tokens):
|
|
336
|
+
if not tok or not tok[0].isalpha():
|
|
337
|
+
continue
|
|
338
|
+
if tok in protected or tok.upper() in protected or tok.lower() in protected:
|
|
342
339
|
continue
|
|
343
|
-
|
|
344
|
-
|
|
340
|
+
if tok in merged:
|
|
341
|
+
# Don't expand record bases (dicts) — leave p in p.x intact
|
|
342
|
+
# so the expression evaluator can resolve the field.
|
|
343
|
+
if isinstance(merged[tok], dict):
|
|
344
|
+
continue
|
|
345
|
+
tokens[i] = str(merged[tok])
|
|
346
|
+
return ''.join(tokens)
|
|
347
|
+
|
|
348
|
+
# Substitute only outside quoted string literals: a variable named in a
|
|
349
|
+
# PRINT label (PRINT "S ="; S) must survive verbatim inside the quotes.
|
|
350
|
+
out: list[str] = []
|
|
351
|
+
i, n = 0, len(stmt)
|
|
352
|
+
while i < n:
|
|
353
|
+
ch = stmt[i]
|
|
354
|
+
if ch in ('"', "'"):
|
|
355
|
+
j = i + 1
|
|
356
|
+
while j < n and stmt[j] != ch:
|
|
357
|
+
j += 1
|
|
358
|
+
out.append(stmt[i:j + 1]) # quoted span, verbatim
|
|
359
|
+
i = j + 1
|
|
360
|
+
else:
|
|
361
|
+
j = i
|
|
362
|
+
while j < n and stmt[j] not in ('"', "'"):
|
|
363
|
+
j += 1
|
|
364
|
+
out.append(_sub_segment(stmt[i:j])) # unquoted span
|
|
365
|
+
i = j
|
|
366
|
+
return ''.join(out)
|
|
345
367
|
|
|
346
368
|
def _expand_statement(self, stmt, _call_stack: set[str] | None = None):
|
|
347
369
|
"""Expand subroutines. Returns list of gate strings.
|
|
@@ -636,18 +658,24 @@ class ExecutorMixin:
|
|
|
636
658
|
|
|
637
659
|
Uses the same _exec_line pipeline as cmd_run for consistency.
|
|
638
660
|
"""
|
|
639
|
-
# In LOCC mode, handle @register prefix via the numpy engine
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
661
|
+
# In LOCC mode, handle @register prefix via the numpy engine. Split
|
|
662
|
+
# colon-compound @REG lines so each gate is applied (a single @REG line
|
|
663
|
+
# is just the one-part case); a bare @REG line keeps prior behavior.
|
|
664
|
+
if self.locc_mode and self.locc and line.strip().startswith('@'):
|
|
665
|
+
parts = self._split_colon_stmts(line) if ':' in line else [line.strip()]
|
|
666
|
+
for part in parts:
|
|
667
|
+
m = RE_AT_REG_LINE.match(part)
|
|
668
|
+
if not m:
|
|
669
|
+
self.io.writeln(f"?BAD @register statement: {part}")
|
|
670
|
+
continue
|
|
643
671
|
reg = m.group(1).upper()
|
|
644
672
|
gate_stmt = m.group(2).strip()
|
|
645
673
|
if reg not in self.locc.names:
|
|
646
674
|
self.io.writeln(f"?UNKNOWN REGISTER: {reg} (have {', '.join(self.locc.names)})")
|
|
647
675
|
return
|
|
648
676
|
self._locc_apply_gate(reg, gate_stmt)
|
|
649
|
-
|
|
650
|
-
|
|
677
|
+
self._locc_state()
|
|
678
|
+
return
|
|
651
679
|
if line.strip().startswith('@'):
|
|
652
680
|
self.io.writeln("?@register syntax requires LOCC mode (try: LOCC <n1> <n2>)")
|
|
653
681
|
return
|
|
@@ -116,7 +116,7 @@ class ExpressionMixin:
|
|
|
116
116
|
'SQRT2': math.sqrt(2), 'sqrt2': math.sqrt(2),
|
|
117
117
|
'True': True, 'False': False,
|
|
118
118
|
}
|
|
119
|
-
_AST_OPS = {
|
|
119
|
+
_AST_OPS: dict[type, Any] = {
|
|
120
120
|
ast.Add: operator.add, ast.Sub: operator.sub,
|
|
121
121
|
ast.Mult: operator.mul, ast.Div: operator.truediv,
|
|
122
122
|
ast.FloorDiv: operator.floordiv, ast.Mod: operator.mod,
|
|
@@ -6,6 +6,7 @@ import os
|
|
|
6
6
|
import re
|
|
7
7
|
import sys
|
|
8
8
|
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
9
10
|
|
|
10
11
|
import numpy as np
|
|
11
12
|
|
|
@@ -99,7 +100,7 @@ class FileIOMixin:
|
|
|
99
100
|
else:
|
|
100
101
|
f.write(f"LET {name} = {val}\n")
|
|
101
102
|
# Save program (exclude lines injected by IMPORT)
|
|
102
|
-
imported = getattr(self, '_imported_lines', set())
|
|
103
|
+
imported: set[int] = getattr(self, '_imported_lines', set())
|
|
103
104
|
saved = [n for n in sorted(self.program.keys()) if n not in imported]
|
|
104
105
|
for num in saved:
|
|
105
106
|
f.write(f"{num} {self.program[num]}\n")
|
|
@@ -242,8 +242,8 @@ def _measure_np(
|
|
|
242
242
|
"""
|
|
243
243
|
sv = np.ascontiguousarray(sv).reshape([2] * n_qubits)
|
|
244
244
|
ax = n_qubits - 1 - qubit
|
|
245
|
-
idx_0 = [slice(None)] * n_qubits
|
|
246
|
-
idx_1 = [slice(None)] * n_qubits
|
|
245
|
+
idx_0: list = [slice(None)] * n_qubits
|
|
246
|
+
idx_1: list = [slice(None)] * n_qubits
|
|
247
247
|
idx_0[ax] = 0
|
|
248
248
|
idx_1[ax] = 1
|
|
249
249
|
p0 = float(np.sum(np.abs(sv[tuple(idx_0)])**2))
|
|
@@ -42,7 +42,7 @@ class LOCCEngine:
|
|
|
42
42
|
self.joint = joint
|
|
43
43
|
self.noise_param = noise_param
|
|
44
44
|
self.noise_type = noise_type
|
|
45
|
-
self.classical = {}
|
|
45
|
+
self.classical: dict[str, int] = {}
|
|
46
46
|
# Precompute offsets for JOINT mode
|
|
47
47
|
self.offsets = []
|
|
48
48
|
off = 0
|
|
@@ -237,8 +237,8 @@ class LOCCEngine:
|
|
|
237
237
|
"""Sample and return (per_reg_counts, joint_counts)."""
|
|
238
238
|
if self.joint:
|
|
239
239
|
raw = _sample_np(self.sv, self.n_total, shots)
|
|
240
|
-
per_reg = {name: {} for name in self.names}
|
|
241
|
-
joint = {}
|
|
240
|
+
per_reg: dict[str, dict[str, int]] = {name: {} for name in self.names}
|
|
241
|
+
joint: dict[str, int] = {}
|
|
242
242
|
for state, count in raw.items():
|
|
243
243
|
# Split the joint bitstring into per-register segments.
|
|
244
244
|
# The joint statevector is ordered A[0..nA-1] B[0..nB-1] ...
|