From 8888307c1ea71f925554bdaed6bdcb950adb4635 Mon Sep 17 00:00:00 2001 From: rturrado Date: Mon, 7 Sep 2026 16:55:39 +0200 Subject: [PATCH 1/7] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Extract=20parseBitRegi?= =?UTF-8?q?sterRef=20helper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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 #462 reuse the same shape when the condition has no comparator. Assisted-by: Claude Opus 4.7 via Claude Code --- src/common/parsing/CodePreprocessing.cpp | 83 ++++++++++++++++-------- 1 file changed, 55 insertions(+), 28 deletions(-) diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index 19409a6..83a3cf2 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -53,6 +53,56 @@ bool isDigits(const std::string& text) { text, [](unsigned char c) { return std::isdigit(c) != 0; }); } +/** + * @brief A reference into a classical bit register. + * + * `bitIndex` holds the index of a single bit within the register. + * When it is `std::nullopt`, the reference targets the whole register + * rather than an individual bit. + */ +struct BitRegisterRef { + std::string name; + std::optional bitIndex; +}; + +/** + * @brief Parse a classical bit register reference from the given text. + * + * Accepts either the bare register name (`c`), which resolves to + * `{name = "c", bitIndex = std::nullopt}` and targets the whole register, + * or the indexed form (`c[k]`), which resolves to a single-bit reference. + * @param text The already-trimmed text to parse. + * @return The parsed reference, or `std::nullopt` if the shape is invalid. + */ +std::optional parseBitRegisterRef(const std::string& text) { + if (text.empty()) { + return std::nullopt; + } + const auto bracketPos = text.find('['); + if (bracketPos == std::string::npos) { + return BitRegisterRef{.name = text, .bitIndex = std::nullopt}; + } + const auto closePos = text.find(']', bracketPos + 1); + if (bracketPos == 0 || closePos == std::string::npos || + closePos != text.size() - 1) { + return std::nullopt; + } + auto base = text.substr(0, bracketPos); + const auto indexText = text.substr(bracketPos + 1, closePos - bracketPos - 1); + if (!isDigits(indexText)) { + return std::nullopt; + } + size_t bitIndex = 0; + try { + bitIndex = std::stoull(indexText); + } catch (const std::invalid_argument&) { + return std::nullopt; + } catch (const std::out_of_range&) { + return std::nullopt; + } + return BitRegisterRef{.name = std::move(base), .bitIndex = bitIndex}; +} + /** * @brief 1-based line/column location within source text. */ @@ -458,35 +508,12 @@ parseClassicConditionExpression(const std::string& condition) { return std::nullopt; } - const auto bracketPos = lhs.find('['); - if (bracketPos != std::string::npos) { - const auto closePos = lhs.find(']', bracketPos + 1); - if (bracketPos == 0 || closePos == std::string::npos || - closePos != lhs.size() - 1) { - return std::nullopt; - } - const auto base = lhs.substr(0, bracketPos); - const auto indexText = - lhs.substr(bracketPos + 1, closePos - bracketPos - 1); - if (!isDigits(indexText)) { - return std::nullopt; - } - size_t bitIndex = 0; - try { - bitIndex = std::stoull(indexText); - } catch (const std::invalid_argument&) { - return std::nullopt; - } catch (const std::out_of_range&) { - return std::nullopt; - } - return ClassicCondition{.registerName = base, - .bitIndex = bitIndex, - .expectedValue = expected, - .kind = match->kind}; + const auto ref = parseBitRegisterRef(lhs); + if (!ref.has_value()) { + return std::nullopt; } - - return ClassicCondition{.registerName = lhs, - .bitIndex = std::nullopt, + return ClassicCondition{.registerName = ref->name, + .bitIndex = ref->bitIndex, .expectedValue = expected, .kind = match->kind}; } From 60a044caee90aadc07ffab858eacc42044960451 Mon Sep 17 00:00:00 2001 From: rturrado Date: Mon, 7 Sep 2026 17:16:47 +0200 Subject: [PATCH 2/7] =?UTF-8?q?=E2=9C=A8=20Support=20bare=20register/bit?= =?UTF-8?q?=20as=20boolean=20condition?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 #462. Assisted-by: Claude Opus 4.7 via Claude Code --- src/common/parsing/CodePreprocessing.cpp | 9 +++ test/test_custom_code.cpp | 78 ++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index 83a3cf2..d5e3484 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -487,6 +487,15 @@ parseClassicConditionExpression(const std::string& condition) { } } if (!match.has_value()) { + // Bare register (`c`) or bit (`c[k]`). + // Treat it as an implicit `!= 0` check so the existing evaluator handles it + // unchanged. + if (const auto ref = parseBitRegisterRef(normalized); ref.has_value()) { + return ClassicCondition{.registerName = ref->name, + .bitIndex = ref->bitIndex, + .expectedValue = 0, + .kind = qc::Neq}; + } return std::nullopt; } const auto opPos = normalized.find(match->text); diff --git a/test/test_custom_code.cpp b/test/test_custom_code.cpp index e55337a..0cd1656 100644 --- a/test/test_custom_code.cpp +++ b/test/test_custom_code.cpp @@ -259,6 +259,84 @@ TEST_F(CustomCodeTest, IfElseOperationBackwardStep) { ASSERT_TRUE(complexEquality(amplitudes[3], 1, 0.0)); } +/** + * @test Test classic-controlled operations that use a bare classical register + * as the condition (no explicit comparator). + * The measured value of `c` is 1, so `if (c)` triggers and `x q[1]` is applied. + */ +TEST_F(CustomCodeTest, IfElseOperationBareRegisterTrue) { + loadCode(2, 1, + "x q[0];" + "cx q[0], q[1];" + "measure q[0] -> c[0];" + "if(c) x q[1];"); + ASSERT_EQ(state->runSimulation(state), OK); + + std::array amplitudes{}; + Statevector sv{2, 4, amplitudes.data()}; + state->getStateVectorFull(state, &sv); + // q[0] = 1, q[1] = 0 after the `if` flips q[1] from 1 to 0. + ASSERT_TRUE(complexEquality(amplitudes[1], 1, 0.0)); +} + +/** + * @test Same as `IfElseOperationBareRegisterTrue` but with the measurement + * producing 0, so `if (c)` does not trigger. + */ +TEST_F(CustomCodeTest, IfElseOperationBareRegisterFalse) { + loadCode(2, 1, + "measure q[0] -> c[0];" + "if(c) x q[1];"); + ASSERT_EQ(state->runSimulation(state), OK); + + std::array amplitudes{}; + Statevector sv{2, 4, amplitudes.data()}; + state->getStateVectorFull(state, &sv); + // Both qubits stay at |0>; the `if` is skipped. + ASSERT_TRUE(complexEquality(amplitudes[0], 1, 0.0)); +} + +/** + * @test Same as `IfElseOperationBareRegisterTrue` but using a single-bit + * reference (`c[0]`) as the bare condition. + */ +TEST_F(CustomCodeTest, IfElseOperationBareBit) { + loadCode(2, 1, + "x q[0];" + "cx q[0], q[1];" + "measure q[0] -> c[0];" + "if(c[0]) x q[1];"); + ASSERT_EQ(state->runSimulation(state), OK); + + std::array amplitudes{}; + Statevector sv{2, 4, amplitudes.data()}; + state->getStateVectorFull(state, &sv); + ASSERT_TRUE(complexEquality(amplitudes[1], 1, 0.0)); +} + +/** + * @test Exercise the backward-execution branch for a bare-register condition. + * Stepping back should undo the `x q[1]` that fired on the forward pass. + */ +TEST_F(CustomCodeTest, IfElseOperationBareRegisterBackwardStep) { + loadCode(2, 1, + "x q[0];" + "cx q[0], q[1];" + "measure q[0] -> c[0];" + "if(c) x q[1];"); + ASSERT_EQ(state->runSimulation(state), OK); + + std::array amplitudes{}; + Statevector sv{2, 4, amplitudes.data()}; + state->getStateVectorFull(state, &sv); + ASSERT_TRUE(complexEquality(amplitudes[1], 1, 0.0)); + + ASSERT_EQ(state->stepBackward(state), OK); + state->getStateVectorFull(state, &sv); + // Undoing the `if(c) x q[1]` restores q[0] = 1, q[1] = 1, i.e. index 3. + ASSERT_TRUE(complexEquality(amplitudes[3], 1, 0.0)); +} + /** * @test Test the `reset` instruction. */ From a8b500051af56cbac93f484a12b1beb615840800 Mon Sep 17 00:00:00 2001 From: rturrado Date: Mon, 7 Sep 2026 18:14:15 +0200 Subject: [PATCH 3/7] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Call=20parseBitRegiste?= =?UTF-8?q?rRef=20only=20once?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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 --- src/common/parsing/CodePreprocessing.cpp | 71 +++++++++++------------- 1 file changed, 33 insertions(+), 38 deletions(-) diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index d5e3484..b5f7862 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -459,11 +459,6 @@ ClassicControlledGate parseClassicControlledGate(const std::string& code) { std::optional parseClassicConditionExpression(const std::string& condition) { - auto normalized = removeWhitespace(condition); - if (!normalized.empty() && normalized.front() == '(') { - normalized.erase(0, 1); - } - // Operators must be scanned longest-first so that "<=" is not misread as "<". struct OperatorMatch { std::string_view text; @@ -479,6 +474,17 @@ parseClassicConditionExpression(const std::string& condition) { {.text = ">", .kind = qc::Gt}, }}; + auto normalized = removeWhitespace(condition); + if (!normalized.empty() && normalized.front() == '(') { + normalized.erase(0, 1); + } + + // Default values for the bare form (`c`, `c[k]`): implicit `!= 0`. + std::string operand{ normalized }; + size_t expected = 0; + qc::ComparisonKind kind = qc::Neq; + + // Comparator form (`c` integer). std::optional match; for (const auto& op : OPERATORS) { if (normalized.find(op.text) != std::string::npos) { @@ -486,45 +492,34 @@ parseClassicConditionExpression(const std::string& condition) { break; } } - if (!match.has_value()) { - // Bare register (`c`) or bit (`c[k]`). - // Treat it as an implicit `!= 0` check so the existing evaluator handles it - // unchanged. - if (const auto ref = parseBitRegisterRef(normalized); ref.has_value()) { - return ClassicCondition{.registerName = ref->name, - .bitIndex = ref->bitIndex, - .expectedValue = 0, - .kind = qc::Neq}; + if (match.has_value()) { + const auto opPos = normalized.find(match->text); + const auto lhs = normalized.substr(0, opPos); + const auto rhs = normalized.substr(opPos + match->text.size()); + if (lhs.empty() || rhs.empty()) { + return std::nullopt; } - return std::nullopt; - } - const auto opPos = normalized.find(match->text); - const auto lhs = normalized.substr(0, opPos); - const auto rhs = normalized.substr(opPos + match->text.size()); - if (lhs.empty() || rhs.empty()) { - return std::nullopt; - } - - if (!isDigits(rhs)) { - return std::nullopt; - } - size_t expected = 0; - try { - expected = std::stoull(rhs); - } catch (const std::invalid_argument&) { - return std::nullopt; - } catch (const std::out_of_range&) { - return std::nullopt; + if (!isDigits(rhs)) { + return std::nullopt; + } + try { + expected = std::stoull(rhs); + } catch (const std::invalid_argument&) { + return std::nullopt; + } catch (const std::out_of_range&) { + return std::nullopt; + } + operand = lhs; + kind = match->kind; } - const auto ref = parseBitRegisterRef(lhs); - if (!ref.has_value()) { - return std::nullopt; - } + if (const auto ref = parseBitRegisterRef(operand); ref.has_value()) { return ClassicCondition{.registerName = ref->name, .bitIndex = ref->bitIndex, .expectedValue = expected, - .kind = match->kind}; + .kind = kind}; + } + return std::nullopt; } std::optional From 9198754b95c5639546d176458209cfa9d2e1355b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 16:14:32 +0000 Subject: [PATCH 4/7] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/parsing/CodePreprocessing.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index b5f7862..43deca4 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -480,7 +480,7 @@ parseClassicConditionExpression(const std::string& condition) { } // Default values for the bare form (`c`, `c[k]`): implicit `!= 0`. - std::string operand{ normalized }; + std::string operand{normalized}; size_t expected = 0; qc::ComparisonKind kind = qc::Neq; @@ -514,10 +514,10 @@ parseClassicConditionExpression(const std::string& condition) { } if (const auto ref = parseBitRegisterRef(operand); ref.has_value()) { - return ClassicCondition{.registerName = ref->name, - .bitIndex = ref->bitIndex, - .expectedValue = expected, - .kind = kind}; + return ClassicCondition{.registerName = ref->name, + .bitIndex = ref->bitIndex, + .expectedValue = expected, + .kind = kind}; } return std::nullopt; } From 71b933ca9d0a50cdb9119ef15684b1282be29a85 Mon Sep 17 00:00:00 2001 From: rturrado Date: Mon, 7 Sep 2026 18:26:45 +0200 Subject: [PATCH 5/7] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Use=20std::from=5Fchar?= =?UTF-8?q?s=20instead=20of=20std::stoull?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/common/parsing/CodePreprocessing.cpp | 49 +++++++++++++++--------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index 43deca4..6e08307 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +34,7 @@ #include #include #include +#include #include #include @@ -53,6 +55,27 @@ bool isDigits(const std::string& text) { text, [](unsigned char c) { return std::isdigit(c) != 0; }); } +/** + * @brief Parse the entire text as an unsigned integer. + * + * The text must contain only characters accepted by `std::from_chars` for the + * target type (digits, no leading sign, no whitespace, no trailing garbage). + * Anything else, including partial matches, fails. + * @param text The text to parse. + * @return The parsed value, or `std::nullopt` if parsing fails or does not + * consume the whole input. + */ +std::optional parseUnsignedInt(std::string_view text) { + size_t value = 0; + const char* const begin = std::to_address(text.begin()); + const char* const end = std::to_address(text.end()); + const auto result = std::from_chars(begin, end, value); + if (result.ec != std::errc{} || result.ptr != end) { + return std::nullopt; + } + return value; +} + /** * @brief A reference into a classical bit register. * @@ -89,18 +112,11 @@ std::optional parseBitRegisterRef(const std::string& text) { } auto base = text.substr(0, bracketPos); const auto indexText = text.substr(bracketPos + 1, closePos - bracketPos - 1); - if (!isDigits(indexText)) { - return std::nullopt; - } - size_t bitIndex = 0; - try { - bitIndex = std::stoull(indexText); - } catch (const std::invalid_argument&) { - return std::nullopt; - } catch (const std::out_of_range&) { - return std::nullopt; + + if (const auto bitIndex = parseUnsignedInt(indexText); bitIndex.has_value()) { + return BitRegisterRef{.name = std::move(base), .bitIndex = *bitIndex}; } - return BitRegisterRef{.name = std::move(base), .bitIndex = bitIndex}; + return std::nullopt; } /** @@ -499,16 +515,11 @@ parseClassicConditionExpression(const std::string& condition) { if (lhs.empty() || rhs.empty()) { return std::nullopt; } - if (!isDigits(rhs)) { - return std::nullopt; - } - try { - expected = std::stoull(rhs); - } catch (const std::invalid_argument&) { - return std::nullopt; - } catch (const std::out_of_range&) { + const auto parsed = parseUnsignedInt(rhs); + if (!parsed.has_value()) { return std::nullopt; } + expected = *parsed; operand = lhs; kind = match->kind; } From ec6288cc403995cb9a1deff71ff99aeeece03dc8 Mon Sep 17 00:00:00 2001 From: rturrado Date: Tue, 8 Sep 2026 13:36:20 +0200 Subject: [PATCH 6/7] =?UTF-8?q?=F0=9F=90=9B=20Assign=20parsed=20optional?= =?UTF-8?q?=20directly=20in=20parseBitRegisterRef?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `parseBitRegisterRef` was dereferencing the `std::optional` 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 --- src/common/parsing/CodePreprocessing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index 6e08307..9f227d3 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -114,7 +114,7 @@ std::optional parseBitRegisterRef(const std::string& text) { const auto indexText = text.substr(bracketPos + 1, closePos - bracketPos - 1); if (const auto bitIndex = parseUnsignedInt(indexText); bitIndex.has_value()) { - return BitRegisterRef{.name = std::move(base), .bitIndex = *bitIndex}; + return BitRegisterRef{.name = std::move(base), .bitIndex = bitIndex}; } return std::nullopt; } From e3212d3e4ed496699b877e18b6d9fbd96216dc84 Mon Sep 17 00:00:00 2001 From: rturrado Date: Wed, 9 Sep 2026 00:35:16 +0200 Subject: [PATCH 7/7] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Unify=20register-refer?= =?UTF-8?q?ence=20parsing=20between=20qubit=20targets=20and=20classical=20?= =?UTF-8?q?conditions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `parseBitRegisterRef` (introduced in #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 `` include: `parseRegisterRef` uses `std::from_chars` (no exceptions), so the direct dependency on the exception types is gone. Behavior is unchanged. Closes #470. Assisted-by: Claude Opus 4.7 via Claude Code --- src/common/parsing/CodePreprocessing.cpp | 65 +++++++++--------------- 1 file changed, 24 insertions(+), 41 deletions(-) diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index 9f227d3..86e2786 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -31,7 +31,6 @@ #include #include #include -#include #include #include #include @@ -77,33 +76,34 @@ std::optional parseUnsignedInt(std::string_view text) { } /** - * @brief A reference into a classical bit register. + * @brief A reference to a register, or to a single element within a register. * - * `bitIndex` holds the index of a single bit within the register. - * When it is `std::nullopt`, the reference targets the whole register - * rather than an individual bit. + * `index` can hold the following values: + * - `std::nullopt`, when the reference targets the whole register (`c`, `q`). + * - the position within the register, when the reference targets a single + * element (`c[k]`, `q[k]`). */ -struct BitRegisterRef { +struct RegisterRef { std::string name; - std::optional bitIndex; + std::optional index; }; /** - * @brief Parse a classical bit register reference from the given text. + * @brief Parse a register reference from the given text. * - * Accepts either the bare register name (`c`), which resolves to - * `{name = "c", bitIndex = std::nullopt}` and targets the whole register, - * or the indexed form (`c[k]`), which resolves to a single-bit reference. + * Accepts either the bare register name (`c`, `q`), which resolves to + * `{name = "c", index = std::nullopt}` and targets the whole register, + * or the indexed form (`c[k]`), which resolves to a single-element reference. * @param text The already-trimmed text to parse. * @return The parsed reference, or `std::nullopt` if the shape is invalid. */ -std::optional parseBitRegisterRef(const std::string& text) { +std::optional parseRegisterRef(const std::string& text) { if (text.empty()) { return std::nullopt; } const auto bracketPos = text.find('['); if (bracketPos == std::string::npos) { - return BitRegisterRef{.name = text, .bitIndex = std::nullopt}; + return RegisterRef{.name = text, .index = std::nullopt}; } const auto closePos = text.find(']', bracketPos + 1); if (bracketPos == 0 || closePos == std::string::npos || @@ -113,8 +113,8 @@ std::optional parseBitRegisterRef(const std::string& text) { auto base = text.substr(0, bracketPos); const auto indexText = text.substr(bracketPos + 1, closePos - bracketPos - 1); - if (const auto bitIndex = parseUnsignedInt(indexText); bitIndex.has_value()) { - return BitRegisterRef{.name = std::move(base), .bitIndex = bitIndex}; + if (const auto index = parseUnsignedInt(indexText); index.has_value()) { + return RegisterRef{.name = std::move(base), .index = index}; } return std::nullopt; } @@ -239,37 +239,20 @@ void validateTargets(const std::string& code, size_t instructionStart, detail += "."; throw makeParseError(code, instructionStart, detail); } - const auto open = target.find('['); - if (open == std::string::npos) { - continue; - } - const auto close = target.find(']', open + 1); - if (open == 0 || close == std::string::npos || close != target.size() - 1) { - throw makeParseError(code, instructionStart, - invalidTargetDetail(target, context), target); - } - const auto registerName = target.substr(0, open); - const auto indexText = target.substr(open + 1, close - open - 1); - if (!isDigits(indexText)) { + const auto ref = parseRegisterRef(target); + if (!ref.has_value()) { throw makeParseError(code, instructionStart, invalidTargetDetail(target, context), target); } - size_t registerIndex = 0; - try { - registerIndex = std::stoul(indexText); - } catch (const std::invalid_argument&) { - throw makeParseError(code, instructionStart, - invalidTargetDetail(target, context), target); - } catch (const std::out_of_range&) { - throw makeParseError(code, instructionStart, - invalidTargetDetail(target, context), target); + if (!ref->index.has_value()) { + continue; } - if (std::ranges::find(shadowedRegisters, registerName) != + if (std::ranges::find(shadowedRegisters, ref->name) != shadowedRegisters.end()) { continue; } - const auto found = definedRegisters.find(registerName); - if (found == definedRegisters.end() || found->second <= registerIndex) { + const auto found = definedRegisters.find(ref->name); + if (found == definedRegisters.end() || found->second <= *ref->index) { throw makeParseError(code, instructionStart, invalidTargetDetail(target, context), target); } @@ -524,9 +507,9 @@ parseClassicConditionExpression(const std::string& condition) { kind = match->kind; } - if (const auto ref = parseBitRegisterRef(operand); ref.has_value()) { + if (const auto ref = parseRegisterRef(operand); ref.has_value()) { return ClassicCondition{.registerName = ref->name, - .bitIndex = ref->bitIndex, + .bitIndex = ref->index, .expectedValue = expected, .kind = kind}; }