Skip to content

Commit 4aceb04

Browse files
committed
fix(PR-6192): keep keyword choice parser outcome private
1 parent 79dd643 commit 4aceb04

2 files changed

Lines changed: 20 additions & 40 deletions

File tree

crates/engine/src/parser/oracle_effect/subject.rs

Lines changed: 20 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3356,9 +3356,8 @@ fn build_keyword_choice_sub_ability(
33563356
// "gains your choice of ...". `parse_keyword_choice_grant` anchors on the
33573357
// bare "gain ..." form, so deconjugate the remainder here first.
33583358
let normalized = deconjugate_verb(remainder);
3359-
let (keywords, duration) = match parse_keyword_choice_grant(&normalized) {
3360-
KeywordChoiceGrant::Parsed { keywords, duration } => (keywords, duration),
3361-
KeywordChoiceGrant::NoMatch | KeywordChoiceGrant::UnsupportedNestedChoice => return None,
3359+
let Ok(Some((keywords, duration))) = parse_keyword_choice_grant(&normalized) else {
3360+
return None;
33623361
};
33633362
let affected = static_affected_for_application(application);
33643363
let choice_duration = duration.clone();
@@ -3378,16 +3377,9 @@ fn build_keyword_choice_sub_ability(
33783377
))
33793378
}
33803379

3381-
pub(super) enum KeywordChoiceGrant {
3382-
NoMatch,
3383-
Parsed {
3384-
keywords: Vec<Keyword>,
3385-
duration: Option<Duration>,
3386-
},
3387-
UnsupportedNestedChoice,
3388-
}
3389-
3390-
pub(super) fn parse_keyword_choice_grant(predicate: &str) -> KeywordChoiceGrant {
3380+
fn parse_keyword_choice_grant(
3381+
predicate: &str,
3382+
) -> Result<Option<(Vec<Keyword>, Option<Duration>)>, ()> {
33913383
let lower = predicate.to_lowercase();
33923384

33933385
// CR 608.2d: "gain your choice of X, Y, or Z" offers a resolution-time choice.
@@ -3401,12 +3393,12 @@ pub(super) fn parse_keyword_choice_grant(predicate: &str) -> KeywordChoiceGrant
34013393
{
34023394
let (keyword_text, duration) = super::strip_trailing_duration(choice_text);
34033395
let Some(items) = super::split_choice_list_items(keyword_text.trim()) else {
3404-
return KeywordChoiceGrant::NoMatch;
3396+
return Ok(None);
34053397
};
34063398
// `separated_list1` succeeds on a single item when there is no separator
34073399
// at all; require ≥2 so a lone keyword is not mistaken for a "choice".
34083400
if items.len() < 2 {
3409-
return KeywordChoiceGrant::NoMatch;
3401+
return Ok(None);
34103402
}
34113403
// An item such as "landwalk of your choice" or "protection from a color
34123404
// of your choice" needs an additional typed choice after the outer menu.
@@ -3417,19 +3409,19 @@ pub(super) fn parse_keyword_choice_grant(predicate: &str) -> KeywordChoiceGrant
34173409
.iter()
34183410
.any(|item| nom_primitives::scan_contains(item, "of your choice"))
34193411
{
3420-
return KeywordChoiceGrant::UnsupportedNestedChoice;
3412+
return Err(());
34213413
}
34223414
let Some(keywords) = items
34233415
.iter()
34243416
.map(|item| parse_granted_keyword_fragment(item.trim()))
34253417
.collect::<Option<Vec<Keyword>>>()
34263418
else {
3427-
return KeywordChoiceGrant::NoMatch;
3419+
return Ok(None);
34283420
};
3429-
return KeywordChoiceGrant::Parsed {
3421+
return Ok(Some((
34303422
keywords,
3431-
duration: duration.or(Some(Duration::UntilEndOfTurn)),
3432-
};
3423+
duration.or(Some(Duration::UntilEndOfTurn)),
3424+
)));
34333425
}
34343426

34353427
// Shape 2: "gain/have protection from X or from the color of your choice"
@@ -3445,7 +3437,7 @@ pub(super) fn parse_keyword_choice_grant(predicate: &str) -> KeywordChoiceGrant
34453437
))
34463438
.parse(lower.as_str())
34473439
.ok() else {
3448-
return KeywordChoiceGrant::NoMatch;
3440+
return Ok(None);
34493441
};
34503442
let (quality_text, duration) = super::strip_trailing_duration(remainder);
34513443
// GUARDRAIL: split on the literal " or from " (NOT " or "). Splitting on
@@ -3457,7 +3449,7 @@ pub(super) fn parse_keyword_choice_grant(predicate: &str) -> KeywordChoiceGrant
34573449
let Some((_, (left, right))) =
34583450
nom_primitives::split_once_on(quality_text.trim(), " or from ").ok()
34593451
else {
3460-
return KeywordChoiceGrant::NoMatch;
3452+
return Ok(None);
34613453
};
34623454
// The halves are bare qualities (the "protection from " prefix is already
34633455
// stripped), so map each with parse_protection_target — NOT
@@ -3466,10 +3458,10 @@ pub(super) fn parse_keyword_choice_grant(predicate: &str) -> KeywordChoiceGrant
34663458
let second = Keyword::Protection(crate::types::keywords::parse_protection_target(
34673459
right.trim(),
34683460
));
3469-
KeywordChoiceGrant::Parsed {
3470-
keywords: vec![first, second],
3471-
duration: duration.or(Some(Duration::UntilEndOfTurn)),
3472-
}
3461+
Ok(Some((
3462+
vec![first, second],
3463+
duration.or(Some(Duration::UntilEndOfTurn)),
3464+
)))
34733465
}
34743466

34753467
fn keyword_choice_branch(
@@ -3499,10 +3491,8 @@ fn build_keyword_choice_clause(
34993491
application: &SubjectApplication,
35003492
predicate: &str,
35013493
) -> Result<Option<ParsedEffectClause>, ()> {
3502-
let (keywords, duration) = match parse_keyword_choice_grant(predicate) {
3503-
KeywordChoiceGrant::NoMatch => return Ok(None),
3504-
KeywordChoiceGrant::Parsed { keywords, duration } => (keywords, duration),
3505-
KeywordChoiceGrant::UnsupportedNestedChoice => return Err(()),
3494+
let Some((keywords, duration)) = parse_keyword_choice_grant(predicate)? else {
3495+
return Ok(None);
35063496
};
35073497
let affected = static_affected_for_application(application);
35083498
let branches = keywords

crates/engine/src/parser/oracle_effect/tests.rs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13525,16 +13525,6 @@ fn assassin_initiate_three_way_keyword_choice_grant_is_generic() {
1352513525
/// must decline rather than advertise the card as supported.
1352613526
#[test]
1352713527
fn nary_keyword_choice_grant_defers_nested_choice_keywords() {
13528-
assert!(
13529-
matches!(
13530-
super::parse_keyword_choice_grant(
13531-
"gain your choice of banding, landwalk of your choice, or protection from a color of your choice until end of turn"
13532-
),
13533-
super::KeywordChoiceGrant::UnsupportedNestedChoice
13534-
),
13535-
"nested keyword choices require their own typed resolution prompts"
13536-
);
13537-
1353813528
let def = parse_effect_chain(
1353913529
"This creature gains your choice of banding, landwalk of your choice, or protection from a color of your choice until end of turn",
1354013530
AbilityKind::Spell,

0 commit comments

Comments
 (0)