Skip to main content

manabrew_engine/parsing/
cost.rs

1//! Semantic names for Forge `Cost$` token identifiers.
2//!
3//! The raw card script still uses strings like `Sac<...>` and `T`; this enum
4//! keeps those spellings in the parsing layer so cost execution can dispatch on
5//! typed variants.
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum CostTokenKind {
9    AddCounter,
10    AddMana,
11    Behold,
12    BeholdExile,
13    Blight,
14    ChooseCard,
15    ChooseColor,
16    ChooseCreatureType,
17    CollectEvidence,
18    DamageYou,
19    Discard,
20    Draw,
21    Enlist,
22    Exert,
23    Exile,
24    ExileAnyGrave,
25    ExileCtrlOrGrave,
26    ExiledMoveToGrave,
27    ExileFromGrave,
28    ExileFromHand,
29    ExileFromStack,
30    ExileFromTop,
31    ExileSameGrave,
32    FlipCoin,
33    Forage,
34    GainControl,
35    GainLife,
36    Mana,
37    Mandatory,
38    Mill,
39    PayEnergy,
40    PayLife,
41    PayShards,
42    PromiseGift,
43    PutCardToLibFromBattlefield,
44    PutCardToLibFromGrave,
45    PutCardToLibFromHand,
46    PutCardToLibFromSameGrave,
47    RemoveAnyCounter,
48    Return,
49    Reveal,
50    RevealChosen,
51    RevealFromExile,
52    RevealOrChoose,
53    RollDice,
54    Sac,
55    SubCounter,
56    Tap,
57    TapXType,
58    Unattach,
59    Untap,
60    UntapYType,
61    Waterbend,
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub struct CostToken<'a> {
66    pub kind: CostTokenKind,
67    pub inner: Option<&'a str>,
68}
69
70impl CostTokenKind {
71    pub fn parse(token: &str) -> Option<CostToken<'_>> {
72        Self::parse_exact(token).or_else(|| Self::parse_prefixed(token))
73    }
74
75    fn parse_exact(token: &str) -> Option<CostToken<'_>> {
76        let kind = match token {
77            "T" | "Tap" => Self::Tap,
78            "Q" | "Untap" => Self::Untap,
79            "Mandatory" => Self::Mandatory,
80            "Forage" => Self::Forage,
81            token if token.starts_with("PromiseGift") => Self::PromiseGift,
82            _ => return None,
83        };
84        Some(CostToken { kind, inner: None })
85    }
86
87    fn parse_prefixed(token: &str) -> Option<CostToken<'_>> {
88        if let Some(inner) = token.strip_prefix("Exert<") {
89            return Some(CostToken {
90                kind: Self::Exert,
91                inner: inner.strip_suffix('>'),
92            });
93        }
94
95        // Longer prefixes must stay before shorter prefixes when the names
96        // overlap, matching Forge's original if/else parser.
97        let prefixes = [
98            (Self::Mana, "Mana<"),
99            (Self::Sac, "Sac<"),
100            (Self::Discard, "Discard<"),
101            (Self::PayLife, "PayLife<"),
102            (Self::SubCounter, "SubCounter<"),
103            (Self::AddCounter, "AddCounter<"),
104            (Self::PayEnergy, "PayEnergy<"),
105            (Self::PayShards, "PayShards<"),
106            (Self::ChooseColor, "ChooseColor<"),
107            (Self::ChooseCreatureType, "ChooseCreatureType<"),
108            (Self::FlipCoin, "FlipCoin<"),
109            (Self::RollDice, "RollDice<"),
110            (Self::ExileFromHand, "ExileFromHand<"),
111            (Self::ExileFromGrave, "ExileFromGrave<"),
112            (Self::ExileFromTop, "ExileFromTop<"),
113            (Self::ExileFromStack, "ExileFromStack<"),
114            (Self::ExileAnyGrave, "ExileAnyGrave<"),
115            (Self::ExileSameGrave, "ExileSameGrave<"),
116            (Self::ExileCtrlOrGrave, "ExileCtrlOrGrave<"),
117            (Self::ExiledMoveToGrave, "ExiledMoveToGrave<"),
118            (Self::Exile, "Exile<"),
119            (Self::Return, "Return<"),
120            (Self::TapXType, "tapXType<"),
121            (Self::UntapYType, "untapYType<"),
122            (Self::DamageYou, "DamageYou<"),
123            (Self::Draw, "Draw<"),
124            (Self::Mill, "Mill<"),
125            (Self::Reveal, "Reveal<"),
126            (Self::ChooseCard, "ChooseCard<"),
127            (Self::RevealFromExile, "RevealFromExile<"),
128            (Self::RevealOrChoose, "RevealOrChoose<"),
129            (Self::RevealChosen, "RevealChosen<"),
130            (Self::BeholdExile, "BeholdExile<"),
131            (Self::Behold, "Behold<"),
132            (Self::GainLife, "GainLife<"),
133            (Self::GainControl, "GainControl<"),
134            (Self::RemoveAnyCounter, "RemoveAnyCounter<"),
135            (Self::Unattach, "Unattach<"),
136            (Self::Waterbend, "Waterbend<"),
137            (Self::AddMana, "AddMana<"),
138            (Self::CollectEvidence, "CollectEvidence<"),
139            (Self::PutCardToLibFromHand, "PutCardToLibFromHand<"),
140            (
141                Self::PutCardToLibFromSameGrave,
142                "PutCardToLibFromSameGrave<",
143            ),
144            (Self::PutCardToLibFromGrave, "PutCardToLibFromGrave<"),
145            (
146                Self::PutCardToLibFromBattlefield,
147                "PutCardToLibFromBattlefield<",
148            ),
149            (Self::Enlist, "Enlist<"),
150            (Self::Blight, "Blight<"),
151        ];
152
153        prefixes.iter().find_map(|(kind, prefix)| {
154            token
155                .strip_prefix(prefix)
156                .and_then(|inner| inner.strip_suffix('>'))
157                .map(|inner| CostToken {
158                    kind: *kind,
159                    inner: Some(inner),
160                })
161        })
162    }
163}