Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions crates/engine/src/parser/oracle_replacement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,14 @@
return Some(def);
}

if nom_primitives::scan_contains(&lower, "if you would scry ")
&& nom_primitives::scan_contains(&lower, "draw that many cards instead")
{
if let Some(def) = parse_scry_that_many_draw_replacement(&lower, &text) {
return Some(def);
}
}
Comment on lines +278 to +284

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.

high

R1. Nom combinators on the first pass — no exceptions

Using scan_contains for parsing dispatch violates the hard architectural rule R1. Parser dispatch should not use string-search helpers like scan_contains or .contains(). Instead, call the parser function directly, allowing it to fail fast via nom combinators.

    if let Some(def) = parse_scry_that_many_draw_replacement(&lower, &text) {
        return Some(def);
    }
References
  1. R1. Nom combinators on the first pass — no exceptions. Every new parser dispatch under crates/engine/src/parser/ must use nom 8.0 combinators or delegate to existing helpers. Findings: any new .contains("..."), .starts_with("..."), .ends_with("..."), .find("..."), or .split_once("...") used for parsing dispatch in non-test parser code. (link)


if let Some(def) = parse_mill_count_replacement(&norm_lower, &text) {
return Some(def);
}
Expand Down Expand Up @@ -4337,6 +4345,32 @@
.parse(input)
}

/// CR 614.1a: "If you would scry N, draw that many cards instead" (Sphinx-class).
fn parse_scry_that_many_draw_replacement(
lower: &str,

Check warning on line 4350 in crates/engine/src/parser/oracle_replacement.rs

View workflow job for this annotation

GitHub Actions / Rust lint (fmt, clippy, parser gate)

Diff in /home/runner/work/phase/phase/crates/engine/src/parser/oracle_replacement.rs
original_text: &str,
) -> Option<ReplacementDefinition> {
let (rest, _) = tag::<_, _, OracleError<'_>>("if you would scry ").parse(lower).ok()?;
let (rest, _) =
take_until::<_, _, OracleError<'_>>(", draw that many cards instead").parse(rest).ok()?;
let (_, _) = tag::<_, _, OracleError<'_>>(", draw that many cards instead")
.parse(rest)
.ok()?;
Some(
ReplacementDefinition::new(ReplacementEvent::Scry)
.execute(AbilityDefinition::new(
AbilityKind::Spell,
Effect::Draw {
count: QuantityExpr::Ref {
qty: QuantityRef::EventContextAmount,
},
target: TargetFilter::Controller,
},
))
.description(original_text.to_string()),
)
}

fn parse_scry_count_replacement(lower: &str, original_text: &str) -> Option<ReplacementDefinition> {
let ((effect_kind, count), rest) = nom_on_lower(lower, lower, |input| {
let (input, _) = tag("if you would scry ").parse(input)?;
Expand Down Expand Up @@ -11818,6 +11852,16 @@
Some(DamageModification::Minus { value: 1 })
);
}

#[test]
fn parses_scry_that_many_draw_instead_replacement() {
let def = parse_replacement_line(
"If you would scry 2, draw that many cards instead.",
"Test Scry Draw",
)
.expect("scry to draw replacement");
assert_eq!(def.event, ReplacementEvent::Scry);
}
}

/// Snapshot tests locking current replacement parser output before/after the IR split.
Expand Down
Loading