Skip to content

fix(parser): parse repeated named control clauses#5114

Open
ntindle wants to merge 3 commits into
phase-rs:mainfrom
ntindle:codex/root30-control-named-clauses
Open

fix(parser): parse repeated named control clauses#5114
ntindle wants to merge 3 commits into
phase-rs:mainfrom
ntindle:codex/root30-control-named-clauses

Conversation

@ntindle

@ntindle ntindle commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Extend the you control ... named ... condition parser so repeated typed named clauses keep their own type/name pairs.
  • Preserve the existing shared-type named-pair path for cards like the Empires cycle.
  • Remove the fixed High Marshal Arguel and Liu Bei entries from the parser misparse backlog root chore: update coverage stats and badges #30.

Root cause

parse_control_named_pair handled you control artifacts named A and B by sharing one parsed type across both names. That same path over-consumed repeated typed clauses:

  • High Marshal Arguel parsed the second condition as Enchantment named "a land named temple of aclazotz".
  • Liu Bei parsed both repeated named permanents as one Named string.

Parse audit

Focused before/after export for root #30 changed exactly these cards:

  • high marshal arguel
  • liu bei, lord of shu

Raw JSON deltas are intentional:

  • High Marshal Arguel: second condition is now Land named "temple of aclazotz" instead of Enchantment named "a land named temple of aclazotz".
  • Liu Bei, Lord of Shu: condition is now Or(IsPresent permanent named Guan Yu, IsPresent permanent named Zhang Fei) instead of one overrun Named value.

coverage-parse-diff on the same focused exports reported no card-parse changes detected.

Review cleanup

Addressed current Gemini review comments in 9b89e5533:

  • Added guarded terminators for common comma-led effect verbs/pronouns so named-card parsing stops before the effect clause.
  • Added verified CR annotations for the named-control helpers (CR 201.2, CR 603.4).
  • Added a regression test covering the new effect-lead terminator family.

Verification

  • cargo fmt --all
  • ./scripts/check-parser-combinators.sh
  • cargo test -p engine --features cli --lib control_named -- --nocapture
  • cargo test -p engine --features cli --lib repeated_typed_named_pair -- --nocapture
  • cargo clippy -p engine --all-targets -- -D warnings
  • focused oracle-gen before/after export for root chore: update coverage stats and badges #30
  • coverage-parse-diff /tmp/phase-root30-search-named-or-before.json /tmp/phase-root30-control-named-after.json
  • review cleanup rerun: cargo fmt --all, ./scripts/check-parser-combinators.sh, cargo test -p engine --features cli --lib control_named -- --nocapture, cargo test -p engine --features cli --lib repeated_typed_named_pair -- --nocapture, cargo clippy -p engine --all-targets -- -D warnings, git diff --check

Note: full workspace cargo clippy --all-targets -- -D warnings is blocked on this host by missing pkg-config/OpenSSL for non-engine targets; the engine crate clippy gate passed.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request enhances the condition parser in crates/engine/src/parser/oracle_nom/condition.rs to support disjunctions in 'you control [type] named [Name1] or a [type] named [Name2]' phrases, resolving backlog issues for cards like High Marshal Arguel and Liu Bei. The feedback suggests expanding the list of condition terminators in parse_control_named_condition_terminator to prevent name parser overruns, and adding mandatory CR annotations to the new rules-touching helper functions per Rule R6.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +732 to 758
fn parse_control_named_condition_terminator(
input: &str,
) -> Result<(&str, ()), nom::Err<OracleError<'_>>> {
alt((
value((), tag(".")),
value(
(),
(
tag(", "),
alt((
value((), eof),
value((), tag("instead")),
value((), tag("then")),
value((), tag("do")),
value((), tag("draw")),
value((), tag("create")),
value((), tag("put")),
value((), tag("sacrifice")),
value((), tag("transform")),
value((), tag("you ")),
value((), tag("target ")),
)),
),
),
))
.parse(input)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

[MEDIUM] Expand the list of condition terminators to prevent overrunning the name parser.

Why it matters: The current list of terminators in parse_control_named_condition_terminator is missing several common MTG action verbs (like exile, destroy, return, discard) and pronouns (like it , its , their , each , all , choose ). If a card's effect starts with one of these words after the comma, the name parser will overrun and consume the effect as part of the card name.

Suggested fix: Add these common keywords to the inner alt block.

fn parse_control_named_condition_terminator(
    input: &str,
) -> Result<(&str, ()), nom::Err<OracleError<'_>>> {
    alt((
        value((), tag(".")),
        value(
            (),
            (
                tag(", "),
                alt((
                    value((), eof),
                    value((), tag("instead")),
                    value((), tag("then")),
                    value((), tag("do")),
                    value((), tag("draw")),
                    value((), tag("create")),
                    value((), tag("put")),
                    value((), tag("sacrifice")),
                    value((), tag("transform")),
                    value((), tag("you ")),
                    value((), tag("target ")),
                    value((), tag("exile")),
                    value((), tag("destroy")),
                    value((), tag("return")),
                    value((), tag("discard")),
                    value((), tag("it ")),
                    value((), tag("its ")),
                    value((), tag("their ")),
                    value((), tag("each ")),
                    value((), tag("all ")),
                    value((), tag("choose ")),
                )),
            ),
        ),
    ))
    .parse(input)
}
References
  1. Avoid verbatim string equality for parsing Oracle phrases as it bypasses the robust nom-based parser and creates fragile matches. Instead, decompose compound phrases into modular, reusable parsers for constituent parts and compose them using idiomatic combinator aggregates. (link)

Comment on lines +711 to +715
/// Consume a named-card member up to a guarded condition boundary. Bare commas
/// stay inside the name so legendary names such as "Guan Yu, Sainted Warrior"
/// survive, while a comma followed by an effect lead remains available to the
/// caller as the condition terminator.
fn parse_control_named_final_name(input: &str) -> OracleResult<'_, &str> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

[MEDIUM] Add CR annotations to the new rules-touching helper functions.

Why it matters: Rule R6 of the repository style guide requires every rules-touching line of engine code to carry a comment of the form CR <number>: <description>. The new helper functions (like parse_control_named_final_name, find_repeated_typed_control_named_pair, etc.) implement card name and condition clause parsing under CR 201.2 and CR 603.4 but lack these annotations.

Suggested fix: Add CR 201.2 or CR 603.4 annotations to the documentation comments of these new helper functions.

References
  1. Rule R6. CR annotations are mandatory and verified. Every rules-touching line of engine code must carry a comment of the form CR : . (link)

@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown

Parse changes introduced by this PR · 3 card(s), 3 signature(s) (baseline: main 07dd0b8357d5)

1 card(s) · static/Continuous · field conditional: named "guan yu, sainted warrior or a permanent named zhang fei, fierce warrior" in battlefield you control permanent is…named "guan yu, sainted warrior" in battlefield you control permanent is present or named "zhang fei, fierce warrior" i…

Examples: Liu Bei, Lord of Shu

1 card(s) · ability/Token · field conditional: instead if (# of named "arguel's blood fast" in battlefield you control enchantment ≥ 1 and # of named "a land named te…instead if (# of named "arguel's blood fast" in battlefield you control enchantment ≥ 1 and # of named "temple of aclaz…

Examples: High Marshal Arguel

1 card(s) · cost/two other cards named Say Its Name from your graveyard · added: two other cards named ~ from your graveyard

Examples: Say Its Name

1 card(s) had Oracle-text changes (errata/reprint) — excluded as non-parser.

@ntindle ntindle marked this pull request as ready for review July 5, 2026 00:56
@ntindle ntindle requested a review from matthewevans as a code owner July 5, 2026 00:56
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

…named-clauses

# Conflicts:
#	docs/parser-misparse-backlog.md
@matthewevans matthewevans self-assigned this Jul 5, 2026
@matthewevans matthewevans added the bug Bug fix label Jul 5, 2026

@matthewevans matthewevans left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update. I found three remaining parser/scope blockers:

[MED] Comma-led add effects now overrun into the second named card. Evidence: crates/engine/src/parser/oracle_nom/condition.rs:745 terminator list lacks add , while Tower Worker has If you control creatures named Mine Worker and Power Plant Worker, add {C}{C}{C} instead. Why it matters: the new guarded comma boundary regresses a printed shared-type named-control clause that the old comma stop would have split. Suggested fix: terminate on , add and add a discriminating Tower Worker-style parser test.

[MED] The named-control parser still materializes exactly two named predicates, so three-name lists collapse names. Evidence: crates/engine/src/parser/oracle_nom/condition.rs:692 returns only (first, second); Helm of Kaldra has Equipment named Helm of Kaldra, Sword of Kaldra, and Shield of Kaldra. Why it matters: the touched seam handles the class as a pair parser, leaving a same-class printed clause misparsed. Suggested fix: parse a named-member list into N predicates, preserving shared-type and repeated-typed members.

[MED] The sticky parse-diff contains an unexplained out-of-scope parser change. Evidence: the coverage-parse-diff sticky reports Say Its Name added cost/two other cards named Say Its Name from your graveyard, while the PR body claims only High Marshal Arguel and Liu Bei changed. Why it matters: parser PRs need the card-level diff reconciled; unexplained gained/changed signatures are possible blast radius. Suggested fix: reconcile the full sticky diff by explaining/regenerating the baseline or eliminating the unrelated Say Its Name change.

@matthewevans matthewevans removed their assignment Jul 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants