qubasic 0.9.0__tar.gz → 0.10.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.
Files changed (71) hide show
  1. {qubasic-0.9.0 → qubasic-0.10.1}/CHANGELOG.md +26 -0
  2. {qubasic-0.9.0/qubasic.egg-info → qubasic-0.10.1}/PKG-INFO +21 -4
  3. {qubasic-0.9.0 → qubasic-0.10.1}/README.md +20 -3
  4. {qubasic-0.9.0 → qubasic-0.10.1}/pyproject.toml +1 -1
  5. {qubasic-0.9.0 → qubasic-0.10.1/qubasic.egg-info}/PKG-INFO +21 -4
  6. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic.egg-info/SOURCES.txt +1 -0
  7. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/__init__.py +1 -1
  8. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/control_flow.py +22 -8
  9. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/expression.py +58 -7
  10. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/noise_mixin.py +8 -1
  11. qubasic-0.10.1/qubasic_core/qec.py +434 -0
  12. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/qol.py +2 -1
  13. qubasic-0.10.1/qubasic_core/resources.py +173 -0
  14. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/strings.py +12 -14
  15. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/terminal.py +19 -7
  16. {qubasic-0.9.0 → qubasic-0.10.1}/tests/test_qubasic.py +152 -0
  17. qubasic-0.9.0/qubasic_core/qec.py +0 -220
  18. {qubasic-0.9.0 → qubasic-0.10.1}/LICENSE +0 -0
  19. {qubasic-0.9.0 → qubasic-0.10.1}/MANIFEST.in +0 -0
  20. {qubasic-0.9.0 → qubasic-0.10.1}/examples/bell.qb +0 -0
  21. {qubasic-0.9.0 → qubasic-0.10.1}/examples/grover3.qb +0 -0
  22. {qubasic-0.9.0 → qubasic-0.10.1}/examples/locc_teleport.qb +0 -0
  23. {qubasic-0.9.0 → qubasic-0.10.1}/examples/sweep_rx.qb +0 -0
  24. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic.egg-info/dependency_links.txt +0 -0
  25. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic.egg-info/entry_points.txt +0 -0
  26. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic.egg-info/requires.txt +0 -0
  27. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic.egg-info/top_level.txt +0 -0
  28. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/__main__.py +0 -0
  29. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/algorithms.py +0 -0
  30. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/algos2.py +0 -0
  31. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/analysis.py +0 -0
  32. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/backend.py +0 -0
  33. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/benchmarking.py +0 -0
  34. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/bosonic.py +0 -0
  35. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/classic.py +0 -0
  36. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/cli.py +0 -0
  37. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/debug.py +0 -0
  38. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/demos.py +0 -0
  39. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/display.py +0 -0
  40. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/dynamics.py +0 -0
  41. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/engine.py +0 -0
  42. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/engine_state.py +0 -0
  43. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/errors.py +0 -0
  44. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/exec_context.py +0 -0
  45. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/executor.py +0 -0
  46. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/file_io.py +0 -0
  47. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/gates.py +0 -0
  48. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/help_text.py +0 -0
  49. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/io_protocol.py +0 -0
  50. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/locc.py +0 -0
  51. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/locc_commands.py +0 -0
  52. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/locc_display.py +0 -0
  53. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/locc_engine.py +0 -0
  54. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/locc_execution.py +0 -0
  55. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/memory.py +0 -0
  56. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/mock_backend.py +0 -0
  57. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/parser.py +0 -0
  58. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/patterns.py +0 -0
  59. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/pauliprop.py +0 -0
  60. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/profiler.py +0 -0
  61. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/program_mgmt.py +0 -0
  62. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/protocol.py +0 -0
  63. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/qudits.py +0 -0
  64. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/scope.py +0 -0
  65. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/screen.py +0 -0
  66. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/state_display.py +0 -0
  67. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/statements.py +0 -0
  68. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/subs.py +0 -0
  69. {qubasic-0.9.0 → qubasic-0.10.1}/qubasic_core/sweep.py +0 -0
  70. {qubasic-0.9.0 → qubasic-0.10.1}/setup.cfg +0 -0
  71. {qubasic-0.9.0 → qubasic-0.10.1}/tests/test_features.py +0 -0
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.1 (2026-06-19)
4
+
5
+ Expression, string, and PRINT fixes in the classic-BASIC layer. The quantum
6
+ engine is unchanged.
7
+
8
+ ### Fixed
9
+ - Math functions resolve in any case: `SQRT`, `SIN`, `ABS`, `INT`, ... now work as well as their lowercase spellings, matching the rest of the language. Runtime and string functions (`RND`, `LEFT$`, ...) are likewise case-insensitive.
10
+ - `AND`, `OR`, `NOT`, `XOR`, and `<>` work in every expression context (`LET`, `PRINT`, gate parameters), not only in `IF` conditions. `AND`/`OR`/`XOR` are bitwise on integers (`6 AND 3` = 2) while preserving operator precedence below comparison, so `IF a > b AND c > d` still groups correctly; `NOT` is logical.
11
+ - String variables are fully usable: `LET s$ = "foo" + "bar"`, `LET t$ = LEFT$(s$, 3)`, and other computed strings store their real value in both immediate and program mode, instead of being mistaken for a quoted literal or forced through `float()`.
12
+ - `PRINT` surfaces evaluation errors instead of silently printing the unevaluated source text. Assigning a string to a numeric variable reports a `TYPE MISMATCH` rather than crashing on a float conversion.
13
+
14
+ ## 0.10.0 (2026-06-19)
15
+
16
+ Completes the frontier set: fault-tolerant QEC extras and a compilation/resources
17
+ group. All offline on Qiskit/Aer/numpy.
18
+
19
+ ### Added
20
+ - Surface code: `QEC SURFACE [d]`, a rotated surface code at odd distance d (validated to correct every weight-1 error).
21
+ - Union-find / matching decoder: exact minimum-weight matching of syndrome defects, scalable and dependency-free, selected with the `UF` flag on `LOGICAL_ERROR_RATE`. It tracks the optimal lookup decoder on the repetition and surface codes; the lookup decoder remains the default and the right choice for non-topological codes like Steane and Shor.
22
+ - Magic-state distillation: `DISTILL <p>` runs the 15-to-1 protocol (via the [15,11,3] Hamming detection), with cubic output-error suppression ~35 p^3.
23
+ - Lattice surgery: `LATTICE <a> <b>` performs the joint Zbar_A Zbar_B measurement that merges two repetition patches (the building block of a lattice-surgery CNOT).
24
+ - Fault-tolerant resource estimation: `RESOURCES <target_pL> <physical_p>` reports the surface-code distance, physical qubits per logical, T-count, T-factory overhead, and an approximate runtime.
25
+ - Calibrated device models: `DEVICE linear|ring|heavyhex|all [n] [T1us] [T2us]` builds a per-qubit thermal-relaxation noise model and coupling map for offline hardware-realistic simulation.
26
+ - `NOISE crosstalk <p>`: a correlated two-qubit ZZ crosstalk channel on entangling gates.
27
+ - `OPTIMIZE [level]`: transpiles the program and reports the depth and gate-count reduction.
28
+
3
29
  ## 0.9.0 (2026-06-19)
4
30
 
5
31
  Frontier and experimental methods. All offline on Qiskit/Aer/numpy; nothing
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qubasic
3
- Version: 0.9.0
3
+ Version: 0.10.1
4
4
  Summary: Quantum BASIC Interactive Terminal
5
5
  Author-email: "Charles C. Norton" <machineelv@gmail.com>
6
6
  License-Expression: MIT
@@ -242,7 +242,7 @@ CLEAR x Remove a variable
242
242
  Arithmetic: `+`, `-`, `*`, `/`, `//`, `%`, `**`
243
243
  Comparison: `==`, `!=`, `<>`, `<`, `>`, `<=`, `>=`
244
244
  Logical: `AND`, `OR`, `NOT`, `XOR`
245
- Bitwise: `AND`, `OR`, `XOR`, `NOT` (on integers)
245
+ Bitwise: `AND`, `OR`, `XOR` on integers (`6 AND 3` = 2); `NOT` is logical
246
246
  Hex/binary literals: `&HFF`, `&B10110`
247
247
 
248
248
  ## Arrays
@@ -514,11 +514,18 @@ CHANNEL AD = [[1,0],[0,0.95]] ; [[0,0.31],[0,0]] Define a Kraus channel
514
514
  ## Error correction
515
515
 
516
516
  ```
517
- QEC STEANE Show a code (REP [d], STEANE, SHOR) and its stabilizers
518
- LOGICAL_ERROR_RATE STEANE 0.02 Monte-Carlo logical error rate at physical p
517
+ QEC STEANE Show a code (REP [d], STEANE, SHOR, SURFACE [d]) and its stabilizers
518
+ LOGICAL_ERROR_RATE STEANE 0.02 Monte-Carlo logical error rate (optimal lookup decoder)
519
+ LOGICAL_ERROR_RATE SURFACE 0.02 UF Same, with the union-find / matching decoder
519
520
  THRESHOLD REP 0.0 0.5 11 Sweep p across distances 3/5/7 (crossing at 0.5)
521
+ DISTILL 0.02 15-to-1 magic-state distillation (output error ~35 p^3)
522
+ LATTICE 0 1 Lattice-surgery joint Zbar-Zbar measurement of two patches
520
523
  ```
521
524
 
525
+ Codes: repetition (any odd distance), Steane [[7,1,3]], Shor [[9,1,3]], rotated
526
+ surface. Decoders: an optimal minimum-weight lookup table (all codes) and a
527
+ scalable union-find / matching decoder (topological codes, via the `UF` flag).
528
+
522
529
  ## Benchmarking and verification
523
530
 
524
531
  ```
@@ -554,6 +561,16 @@ BOSONIC 1 25 Continuous-variable Fock modes
554
561
  DISPLACE 0 1.0 ...DISPLACE, SQUEEZE, CAT, BS, BSTATE
555
562
  ```
556
563
 
564
+ ## Compilation and resources
565
+
566
+ ```
567
+ RESOURCES 1e-12 0.001 Fault-tolerant estimate (surface-code distance, qubits, runtime)
568
+ DEVICE linear 5 Calibrated offline device model (per-qubit T1/T2 + coupling map)
569
+ DEVICE ring 5 80 60 ...with custom T1/T2 in microseconds (also: heavyhex, all, OFF)
570
+ OPTIMIZE 3 Transpile the program and report the depth/gate reduction
571
+ NOISE crosstalk 0.01 Correlated two-qubit ZZ crosstalk on entangling gates
572
+ ```
573
+
557
574
  ## Noise models
558
575
 
559
576
  ```
@@ -209,7 +209,7 @@ CLEAR x Remove a variable
209
209
  Arithmetic: `+`, `-`, `*`, `/`, `//`, `%`, `**`
210
210
  Comparison: `==`, `!=`, `<>`, `<`, `>`, `<=`, `>=`
211
211
  Logical: `AND`, `OR`, `NOT`, `XOR`
212
- Bitwise: `AND`, `OR`, `XOR`, `NOT` (on integers)
212
+ Bitwise: `AND`, `OR`, `XOR` on integers (`6 AND 3` = 2); `NOT` is logical
213
213
  Hex/binary literals: `&HFF`, `&B10110`
214
214
 
215
215
  ## Arrays
@@ -481,11 +481,18 @@ CHANNEL AD = [[1,0],[0,0.95]] ; [[0,0.31],[0,0]] Define a Kraus channel
481
481
  ## Error correction
482
482
 
483
483
  ```
484
- QEC STEANE Show a code (REP [d], STEANE, SHOR) and its stabilizers
485
- LOGICAL_ERROR_RATE STEANE 0.02 Monte-Carlo logical error rate at physical p
484
+ QEC STEANE Show a code (REP [d], STEANE, SHOR, SURFACE [d]) and its stabilizers
485
+ LOGICAL_ERROR_RATE STEANE 0.02 Monte-Carlo logical error rate (optimal lookup decoder)
486
+ LOGICAL_ERROR_RATE SURFACE 0.02 UF Same, with the union-find / matching decoder
486
487
  THRESHOLD REP 0.0 0.5 11 Sweep p across distances 3/5/7 (crossing at 0.5)
488
+ DISTILL 0.02 15-to-1 magic-state distillation (output error ~35 p^3)
489
+ LATTICE 0 1 Lattice-surgery joint Zbar-Zbar measurement of two patches
487
490
  ```
488
491
 
492
+ Codes: repetition (any odd distance), Steane [[7,1,3]], Shor [[9,1,3]], rotated
493
+ surface. Decoders: an optimal minimum-weight lookup table (all codes) and a
494
+ scalable union-find / matching decoder (topological codes, via the `UF` flag).
495
+
489
496
  ## Benchmarking and verification
490
497
 
491
498
  ```
@@ -521,6 +528,16 @@ BOSONIC 1 25 Continuous-variable Fock modes
521
528
  DISPLACE 0 1.0 ...DISPLACE, SQUEEZE, CAT, BS, BSTATE
522
529
  ```
523
530
 
531
+ ## Compilation and resources
532
+
533
+ ```
534
+ RESOURCES 1e-12 0.001 Fault-tolerant estimate (surface-code distance, qubits, runtime)
535
+ DEVICE linear 5 Calibrated offline device model (per-qubit T1/T2 + coupling map)
536
+ DEVICE ring 5 80 60 ...with custom T1/T2 in microseconds (also: heavyhex, all, OFF)
537
+ OPTIMIZE 3 Transpile the program and report the depth/gate reduction
538
+ NOISE crosstalk 0.01 Correlated two-qubit ZZ crosstalk on entangling gates
539
+ ```
540
+
524
541
  ## Noise models
525
542
 
526
543
  ```
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "qubasic"
7
- version = "0.9.0"
7
+ version = "0.10.1"
8
8
  description = "Quantum BASIC Interactive Terminal"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qubasic
3
- Version: 0.9.0
3
+ Version: 0.10.1
4
4
  Summary: Quantum BASIC Interactive Terminal
5
5
  Author-email: "Charles C. Norton" <machineelv@gmail.com>
6
6
  License-Expression: MIT
@@ -242,7 +242,7 @@ CLEAR x Remove a variable
242
242
  Arithmetic: `+`, `-`, `*`, `/`, `//`, `%`, `**`
243
243
  Comparison: `==`, `!=`, `<>`, `<`, `>`, `<=`, `>=`
244
244
  Logical: `AND`, `OR`, `NOT`, `XOR`
245
- Bitwise: `AND`, `OR`, `XOR`, `NOT` (on integers)
245
+ Bitwise: `AND`, `OR`, `XOR` on integers (`6 AND 3` = 2); `NOT` is logical
246
246
  Hex/binary literals: `&HFF`, `&B10110`
247
247
 
248
248
  ## Arrays
@@ -514,11 +514,18 @@ CHANNEL AD = [[1,0],[0,0.95]] ; [[0,0.31],[0,0]] Define a Kraus channel
514
514
  ## Error correction
515
515
 
516
516
  ```
517
- QEC STEANE Show a code (REP [d], STEANE, SHOR) and its stabilizers
518
- LOGICAL_ERROR_RATE STEANE 0.02 Monte-Carlo logical error rate at physical p
517
+ QEC STEANE Show a code (REP [d], STEANE, SHOR, SURFACE [d]) and its stabilizers
518
+ LOGICAL_ERROR_RATE STEANE 0.02 Monte-Carlo logical error rate (optimal lookup decoder)
519
+ LOGICAL_ERROR_RATE SURFACE 0.02 UF Same, with the union-find / matching decoder
519
520
  THRESHOLD REP 0.0 0.5 11 Sweep p across distances 3/5/7 (crossing at 0.5)
521
+ DISTILL 0.02 15-to-1 magic-state distillation (output error ~35 p^3)
522
+ LATTICE 0 1 Lattice-surgery joint Zbar-Zbar measurement of two patches
520
523
  ```
521
524
 
525
+ Codes: repetition (any odd distance), Steane [[7,1,3]], Shor [[9,1,3]], rotated
526
+ surface. Decoders: an optimal minimum-weight lookup table (all codes) and a
527
+ scalable union-find / matching decoder (topological codes, via the `UF` flag).
528
+
522
529
  ## Benchmarking and verification
523
530
 
524
531
  ```
@@ -554,6 +561,16 @@ BOSONIC 1 25 Continuous-variable Fock modes
554
561
  DISPLACE 0 1.0 ...DISPLACE, SQUEEZE, CAT, BS, BSTATE
555
562
  ```
556
563
 
564
+ ## Compilation and resources
565
+
566
+ ```
567
+ RESOURCES 1e-12 0.001 Fault-tolerant estimate (surface-code distance, qubits, runtime)
568
+ DEVICE linear 5 Calibrated offline device model (per-qubit T1/T2 + coupling map)
569
+ DEVICE ring 5 80 60 ...with custom T1/T2 in microseconds (also: heavyhex, all, OFF)
570
+ OPTIMIZE 3 Transpile the program and report the depth/gate reduction
571
+ NOISE crosstalk 0.01 Correlated two-qubit ZZ crosstalk on entangling gates
572
+ ```
573
+
557
574
  ## Noise models
558
575
 
559
576
  ```
@@ -55,6 +55,7 @@ qubasic_core/protocol.py
55
55
  qubasic_core/qec.py
56
56
  qubasic_core/qol.py
57
57
  qubasic_core/qudits.py
58
+ qubasic_core/resources.py
58
59
  qubasic_core/scope.py
59
60
  qubasic_core/screen.py
60
61
  qubasic_core/state_display.py
@@ -28,7 +28,7 @@ __all__ = [
28
28
  'GATE_TABLE', 'GATE_ALIASES',
29
29
  ]
30
30
 
31
- __version__ = '0.9.0'
31
+ __version__ = '0.10.1'
32
32
 
33
33
  def __getattr__(name):
34
34
  """Lazy import heavy modules on first access."""
@@ -12,7 +12,7 @@ from qubasic_core.engine import ExecResult, ExecOutcome
12
12
  from qubasic_core.parser import parse_stmt
13
13
  from qubasic_core.statements import (
14
14
  RawStmt, RemStmt, MeasureStmt, EndStmt, ReturnStmt, WendStmt,
15
- LetArrayStmt, LetStmt, PrintStmt, GotoStmt, GosubStmt,
15
+ LetArrayStmt, LetStmt, LetStrStmt, PrintStmt, GotoStmt, GosubStmt,
16
16
  ForStmt, NextStmt, WhileStmt, IfThenStmt,
17
17
  DataStmt, ReadStmt, OnGotoStmt, OnGosubStmt,
18
18
  SelectCaseStmt, CaseStmt, EndSelectStmt, ElseStmt, EndIfStmt,
@@ -106,7 +106,20 @@ class ControlFlowMixin:
106
106
  def _cf_let_var(self, stmt: str, run_vars: dict[str, Any],
107
107
  parsed: LetStmt) -> tuple[bool, ExecOutcome]:
108
108
  name, expr = parsed.name, parsed.expr
109
- val = self._eval_with_vars(expr, run_vars)
109
+ raw = self._safe_eval(expr, extra_ns=run_vars)
110
+ if isinstance(raw, str):
111
+ raise RuntimeError(
112
+ f"TYPE MISMATCH: '{name}' is numeric; use '{name}$' for strings")
113
+ val = float(raw)
114
+ run_vars[name] = val
115
+ self.variables[name] = val
116
+ return True, ExecResult.ADVANCE
117
+
118
+ def _cf_let_str(self, stmt: str, run_vars: dict[str, Any],
119
+ parsed: LetStrStmt) -> tuple[bool, ExecOutcome]:
120
+ """LET v$ = <expr> — assign a string (or number) to a string variable."""
121
+ name, expr = parsed.name, parsed.expr
122
+ val = self._eval_string_expr(expr, run_vars)
110
123
  run_vars[name] = val
111
124
  self.variables[name] = val
112
125
  return True, ExecResult.ADVANCE
@@ -167,12 +180,12 @@ class ControlFlowMixin:
167
180
  text = re.sub(r'\bTAB\s*\(([^)]+)\)', _spaces, text, flags=re.IGNORECASE)
168
181
  if not text.strip():
169
182
  return text # standalone SPC/TAB -> whitespace
170
- try:
171
- ns = run_vars.as_dict() if hasattr(run_vars, 'as_dict') else (
172
- run_vars if isinstance(run_vars, dict) else dict(run_vars))
173
- return str(self._safe_eval(text, extra_ns=ns))
174
- except Exception:
175
- return text
183
+ ns = run_vars.as_dict() if hasattr(run_vars, 'as_dict') else (
184
+ run_vars if isinstance(run_vars, dict) else dict(run_vars))
185
+ # Surface evaluation errors instead of silently printing the raw source
186
+ # text (which used to turn PRINT SQRT(9) into the literal "SQRT(9)" and
187
+ # an undefined variable into its own name).
188
+ return str(self._safe_eval(text, extra_ns=ns))
176
189
 
177
190
  def _cf_print(self, stmt: str, run_vars: dict[str, Any],
178
191
  parsed: PrintStmt) -> tuple[bool, ExecOutcome]:
@@ -400,6 +413,7 @@ class ControlFlowMixin:
400
413
  WendStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_wend(rv, ls, sl, ip),
401
414
  LetArrayStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_let_array(st, rv, p),
402
415
  LetStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_let_var(st, rv, p),
416
+ LetStrStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_let_str(st, rv, p),
403
417
  PrintStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_print(st, rv, p),
404
418
  GotoStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_goto(st, sl, p),
405
419
  GosubStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_gosub(st, sl, ip, p),
@@ -94,6 +94,36 @@ def _rewrite_logical_outside_strings(cond: str) -> str:
94
94
  return ''.join(parts)
95
95
 
96
96
 
97
+ def _as_int(x: Any) -> int:
98
+ """Coerce a BASIC value to int for bitwise AND/OR/XOR.
99
+
100
+ QBasic rounds non-integers; truth values and non-numerics fold to 1/0 so
101
+ ``(a > 0) AND (b > 0)`` works the same as ``6 AND 3``.
102
+ """
103
+ if isinstance(x, bool):
104
+ return int(x)
105
+ if isinstance(x, int):
106
+ return x
107
+ if isinstance(x, float):
108
+ return int(round(x))
109
+ return 1 if x else 0
110
+
111
+
112
+ def _basic_and(a: Any, b: Any) -> int:
113
+ """BASIC AND — bitwise on integers, and correct for truth values
114
+ (1 & 1 == 1, 1 & 0 == 0). Parsed from ``and`` so precedence stays below
115
+ comparison: ``a > b AND c > d`` groups as ``(a>b) AND (c>d)``."""
116
+ return _as_int(a) & _as_int(b)
117
+
118
+
119
+ def _basic_or(a: Any, b: Any) -> int:
120
+ return _as_int(a) | _as_int(b)
121
+
122
+
123
+ def _basic_xor(a: Any, b: Any) -> int:
124
+ return _as_int(a) ^ _as_int(b)
125
+
126
+
97
127
  class ExpressionMixin:
98
128
  """AST-based safe expression evaluation. No eval().
99
129
 
@@ -126,11 +156,14 @@ class ExpressionMixin:
126
156
  ast.Eq: operator.eq, ast.NotEq: operator.ne,
127
157
  ast.Lt: operator.lt, ast.LtE: operator.le,
128
158
  ast.Gt: operator.gt, ast.GtE: operator.ge,
129
- ast.And: lambda a, b: a and b,
130
- ast.Or: lambda a, b: a or b,
131
- ast.BitAnd: operator.and_,
132
- ast.BitOr: operator.or_,
133
- ast.BitXor: operator.xor,
159
+ # AND/OR/XOR are BASIC's bitwise-logical operators (6 AND 3 == 2),
160
+ # robust to float operands; NOT stays logical so IF NOT flag works
161
+ # with 0/1 truth values.
162
+ ast.And: _basic_and,
163
+ ast.Or: _basic_or,
164
+ ast.BitAnd: _basic_and,
165
+ ast.BitOr: _basic_or,
166
+ ast.BitXor: _basic_xor,
134
167
  ast.Invert: operator.invert,
135
168
  }
136
169
 
@@ -237,6 +270,13 @@ class ExpressionMixin:
237
270
  ns['FRE'] = lambda x=0: psutil.virtual_memory().available
238
271
  except ImportError:
239
272
  ns['FRE'] = lambda x=0: 0
273
+ # BASIC is case-insensitive for built-in functions and constants, so
274
+ # register upper- and lower-case aliases for every builtin (SQRT and
275
+ # sqrt, RND and rnd, LEFT$ and left$). Variables are merged with their
276
+ # own case in _safe_eval and shadow these.
277
+ for _name in list(ns.keys()):
278
+ ns.setdefault(_name.upper(), ns[_name])
279
+ ns.setdefault(_name.lower(), ns[_name])
240
280
  self._base_ns = ns
241
281
  return ns
242
282
 
@@ -301,9 +341,17 @@ class ExpressionMixin:
301
341
  expr_str = str(expr).strip()
302
342
  # Normalize FN prefix: "FN square(x)" -> "square(x)"
303
343
  expr_str = re.sub(r'\bFN\s+(\w+)\s*\(', r'\1(', expr_str, flags=re.IGNORECASE)
344
+ # Rewrite BASIC logical/relational operators (AND, OR, NOT, XOR, <>) to
345
+ # the Python forms the AST understands, only outside quoted strings.
346
+ # Applied to every expression (LET, PRINT, gate params), not just IF
347
+ # conditions, so the documented operators work everywhere.
348
+ expr_str = _rewrite_logical_outside_strings(expr_str)
304
349
  # Rewrite numeric literals (&H, &B, $hex addresses) and the string
305
350
  # sigil, each only outside quoted string literals.
306
351
  expr_str = _replace_dollar_outside_strings(expr_str)
352
+ # The operator rewrite can pad a leading token (NOT x -> " not x"); strip
353
+ # so ast.parse(mode='eval') does not reject it as an unexpected indent.
354
+ expr_str = expr_str.strip()
307
355
  if not expr_str:
308
356
  raise ValueError("EMPTY EXPRESSION")
309
357
  try:
@@ -351,8 +399,11 @@ class ExpressionMixin:
351
399
  return float(self._safe_eval(expr, extra_ns=run_vars))
352
400
 
353
401
  def _eval_condition(self, cond: str, run_vars: dict[str, Any]) -> bool:
354
- """Evaluate a boolean condition."""
355
- cond = _rewrite_logical_outside_strings(cond)
402
+ """Evaluate a boolean condition.
403
+
404
+ The operator rewrite (AND/OR/NOT/XOR/<>) now happens inside _safe_eval,
405
+ so conditions and ordinary expressions share one consistent path.
406
+ """
356
407
  return bool(self._safe_eval(cond, extra_ns=run_vars))
357
408
 
358
409
  def _run_timer(self) -> float:
@@ -106,10 +106,17 @@ class NoiseMixin:
106
106
  p1 = float(parts[2]) if len(parts) > 2 else 0.01
107
107
  nm.add_all_qubit_quantum_error(reset_error(p0, p1), _1q)
108
108
  self.io.writeln(f"NOISE reset p0={p0} p1={p1}")
109
+ elif ntype == 'crosstalk':
110
+ # Correlated (non-product) two-qubit error after each 2-qubit gate:
111
+ # a ZZ Pauli with probability p, otherwise identity.
112
+ p = float(parts[1]) if len(parts) > 1 else 0.01
113
+ err = pauli_error([('ZZ', p), ('II', 1 - p)])
114
+ nm.add_all_qubit_quantum_error(err, _2q)
115
+ self.io.writeln(f"NOISE crosstalk (correlated ZZ on 2-qubit gates) p={p}")
109
116
  else:
110
117
  self.io.writeln(f"?UNKNOWN NOISE TYPE: {ntype}")
111
118
  self.io.writeln(" Types: depolarizing, amplitude_damping, phase_flip, thermal,")
112
- self.io.writeln(" readout, combined, pauli, reset")
119
+ self.io.writeln(" readout, combined, pauli, reset, crosstalk")
113
120
  return
114
121
  self._noise_model = nm
115
122
  self._noise_spec = rest.strip() # for SAVE round-tripping