✨ Support if (c) and if (c[k]) as boolean-only conditions - #463
Conversation
`parseClassicConditionExpression` had inline logic for parsing a bare register (`c`) or a single-bit reference (`c[k]`) after the comparator operand. Move that logic into a `parseBitRegisterRef` helper in the anonymous namespace and use it at the existing call site. The helper returns a `BitRegisterRef` where a null `bitIndex` means "the whole register". Behavior is unchanged; the helper lets the follow-up commit for munich-quantum-toolkit#462 reuse the same shape when the condition has no comparator. Assisted-by: Claude Opus 4.7 via Claude Code
OpenQASM 3 allows a classical register or a single bit as a boolean condition without an explicit comparator: `if (c) x q[0];` and `if (c[0]) x q[0];`. The parser used to reject these forms. When no comparator is found, use `parseBitRegisterRef` to parse the operand and populate `ClassicCondition` with `.kind = qc::Neq` and `.expectedValue = 0`, so the existing evaluator treats the condition as "the value is non-zero". Add end-to-end tests covering the satisfied and unsatisfied cases for a bare register, a bare bit, and a backward step. Closes munich-quantum-toolkit#462. Assisted-by: Claude Opus 4.7 via Claude Code
|
🤖 AI text below 🤖 A few follow-up ideas that surfaced during the review of this PR. Sharing them in case any of them is worth its own issue.
Happy to open any of these as separate issues. |
|
@coderabbitai full review |
✅ Action performedFull review finished. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Team Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 SummarySummary by CodeRabbit
WalkthroughThe parser now accepts bare classical registers and indexed classical bits as implicit non-zero conditions. Tests cover triggered, skipped, indexed-bit, and backward-step execution. ChangesBare classical conditions
Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk: ⚪ Minimal · up to Bare classical register and bit conditions now execute as non-zero checks, with covered satisfied, unsatisfied, indexed-bit, and backward-step behavior. No current merge-blocking risk remains. Sequence Diagram(s)sequenceDiagram
participant CustomCodeTest
participant parseClassicConditionExpression
participant parseBitRegisterRef
CustomCodeTest->>parseClassicConditionExpression: parse if(c) or if(c[0])
parseClassicConditionExpression->>parseBitRegisterRef: parse classical reference
parseBitRegisterRef-->>parseClassicConditionExpression: return parsed reference
parseClassicConditionExpression-->>CustomCodeTest: create implicit != 0 condition
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. A rabbit checks the register bright Comment |
if (c) and if (c[k]) as boolean-only conditions
`parseClassicConditionExpression` was calling `parseBitRegisterRef` twice (once for the operand of the comparator form and once for the whole condition in the bare form) and building the `ClassicCondition` in two places, with only the expected value and comparison kind differing between them. Compute the operand text, the expected value, and the comparison kind first (with defaults suitable for the bare case), then parse the register/bit reference and build the `ClassicCondition` once at the end. Behavior is unchanged. Assisted-by: Claude Opus 4.7 via Claude Code
Replace the two `std::stoull` + `try/catch` blocks (in `parseClassicConditionExpression` and `parseBitRegisterRef`) with a `parseUnsignedInt(std::string_view)` helper backed by `std::from_chars`. `std::from_chars` on integer types is non-throwing (returns `std::errc` in a struct), faster (no locale, no exception machinery), and strict about the input (rejects empty text, leading signs, trailing garbage). The previous `isDigits()` guards at these two sites are no longer needed and go away; `isDigits()` itself stays because `validateTargets` still uses it. `from_chars` needs raw pointers rather than iterators. `std::to_address(text.begin())` / `end()` extracts the pointer on all standard libraries, so no pointer arithmetic and no `NOLINT` are required. Behavior is unchanged. Assisted-by: Claude Opus 4.7 via Claude Code
`parseBitRegisterRef` was dereferencing the `std::optional<size_t>` returned by `parseUnsignedInt` and immediately re-wrapping it as the `.bitIndex` member of `BitRegisterRef`. clang-tidy's `bugprone-optional-value-conversion` flagged the pattern as potentially error-prone. Assign the optional directly instead. Assisted-by: Claude Opus 4.7 via Claude Code
…al conditions `parseBitRegisterRef` (introduced in munich-quantum-toolkit#463) and the parsing loop inside `validateTargets` both walked a `name` or `name[index]` shape with almost identical logic. `parseBitRegisterRef` served only the classical-condition path and `validateTargets` served only qubit targets, so the shared shape lived in two places and could drift. Rename `BitRegisterRef` and `parseBitRegisterRef` to the generic `RegisterRef` and `parseRegisterRef`, and use the helper inside `validateTargets` for the structural parsing. `validateTargets` keeps its qubit-specific semantic checks (shadowedRegisters, definedRegisters existence, index bounds) on top of the parsed result. Also drop the `<stdexcept>` include: `parseRegisterRef` uses `std::from_chars` (no exceptions), so the direct dependency on the exception types is gone. Behavior is unchanged. Closes munich-quantum-toolkit#470. Assisted-by: Claude Opus 4.7 via Claude Code
Description
🤖 AI text below 🤖
Let the debugger evaluate classic-controlled
ifconditions written without an explicit comparator,treating the operand as an implicit "non-zero" check.
OpenQASM 3 allows a classical register or a single bit as a boolean condition with no explicit comparator:
Before this PR the debugger's parser rejected these forms (returned
nullopt), so the classic-controlled gate failed to parse. mqt-core'sIfElseOperationalready models the shape, so only the debugger front-end needed to catch up.Implementation
parseClassicConditionExpressionscans the condition text for one of the six comparators (==,!=,<,<=,>,>=). If none is found, the whole text is now treated as a bare register or bit reference (corc[k]) and interpreted as an implicit!= 0check. The bare form populatesClassicConditionwith.kind = qc::Neqand.expectedValue = 0, so the existing evaluator handles both forms with the same code path.The structural parsing of
nameorname[index]lives in a sharedparseRegisterRefhelper used both by the classic-condition path and byvalidateTargets(for qubit targets in gate calls). Numeric parsing goes through aparseUnsignedInthelper backed bystd::from_chars, so no exceptions are thrown and noisDigitspre-check is needed.End-to-end tests in
test_custom_code.cppcover the satisfied case, the unsatisfied case, the single-bit form, and one backward-step case, mirroring the coverage added in PR #456.Closes #470.
AI assistance
Commit messages, code changes, and this PR body were drafted with Claude Opus 4.7 via Claude Code.
All content was reviewed and edited manually before submission.
Fixes #462.
Checklist
If PR contains AI-assisted content:
🤖 *AI text below* 🤖(titles are exempt).