figure_skating_element_parser/
lib.rs

1#![doc = include_str!("../docs.md")]
2
3use ::pest_derive::Parser;
4use pest::Parser;
5use thiserror::Error;
6
7#[derive(Parser)]
8#[grammar = "./grammar.pest"]
9pub struct ElementParser;
10
11#[derive(Debug)]
12pub struct ParsedElement {
13    pub element_type: String,
14    pub full_name: String,
15    pub base_value: f32,
16}
17
18#[derive(Debug, Error)]
19pub enum ParseError {
20    #[error("Failed to parse element: {0}")]
21    ParseError(String),
22}
23
24impl ParsedElement {
25    pub fn from_text(text: &str) -> Result<Vec<Self>, ParseError> {
26        let mut elements = Vec::new();
27
28        for line in text.split_whitespace() {
29            let pairs = ElementParser::parse(Rule::element, line)
30                .map_err(|e| ParseError::ParseError(e.to_string()))?;
31
32            for pair in pairs {
33                if pair.as_rule() == Rule::element {
34                    let mut element_type = String::new();
35                    let mut full_name = String::new();
36                    let mut base_value = 0.0;
37
38                    for inner_pair in pair.into_inner() {
39                        match inner_pair.as_rule() {
40                            Rule::jump => {
41                                element_type = "Jump".to_string();
42                                full_name = get_full_name(inner_pair.as_str());
43                                base_value = get_base_value(inner_pair.as_str());
44                            }
45                            Rule::spin => {
46                                element_type = "Spin".to_string();
47                                full_name = get_full_name(inner_pair.as_str());
48                                base_value = get_base_value(inner_pair.as_str());
49                            }
50                            Rule::step_sequence => {
51                                element_type = "Step Sequence".to_string();
52                                full_name = get_full_name(inner_pair.as_str());
53                                base_value = get_base_value(inner_pair.as_str());
54                            }
55
56                            Rule::death_spiral => {
57                                element_type = "Death Spiral".to_string();
58                                full_name = get_full_name(inner_pair.as_str());
59                                base_value = get_base_value(inner_pair.as_str());
60                            }
61                            Rule::pair_spin => {
62                                element_type = "Pair Spin".to_string();
63                                full_name = get_full_name(inner_pair.as_str());
64                                base_value = get_base_value(inner_pair.as_str());
65                            }
66                            Rule::twizzle => {
67                                element_type = "Twizzle".to_string();
68                                full_name = get_full_name(inner_pair.as_str());
69                                base_value = get_base_value(inner_pair.as_str());
70                            }
71                            Rule::choreographic_element => {
72                                element_type = "Choreographic Element".to_string();
73                                full_name = get_full_name(inner_pair.as_str());
74                                base_value = get_base_value(inner_pair.as_str());
75                            }
76                            _ => {}
77                        }
78                    }
79
80                    elements.push(ParsedElement {
81                        element_type,
82                        full_name,
83                        base_value,
84                    });
85                }
86            }
87        }
88
89        Ok(elements)
90    }
91}
92
93fn get_full_name(code: &str) -> String {
94    match code {
95        // Jump types
96        "1T" => "Single Toeloop".to_string(),
97        "2T" => "Double Toeloop".to_string(),
98        "3T" => "Triple Toeloop".to_string(),
99        "4T" => "Quad Toeloop".to_string(),
100        "1S" => "Single Salchow".to_string(),
101        "2S" => "Double Salchow".to_string(),
102        "3S" => "Triple Salchow".to_string(),
103        "4S" => "Quad Salchow".to_string(),
104        "1Lo" => "Single Loop".to_string(),
105        "2Lo" => "Double Loop".to_string(),
106        "3Lo" => "Triple Loop".to_string(),
107        "4Lo" => "Quad Loop".to_string(),
108        "1F" => "Single Flip".to_string(),
109        "2F" => "Double Flip".to_string(),
110        "3F" => "Triple Flip".to_string(),
111        "4F" => "Quad Flip".to_string(),
112        "1Lz" => "Single Lutz".to_string(),
113        "2Lz" => "Double Lutz".to_string(),
114        "3Lz" => "Triple Lutz".to_string(),
115        "4Lz" => "Quad Lutz".to_string(),
116        "1A" => "Single Axel".to_string(),
117        "2A" => "Double Axel".to_string(),
118        "3A" => "Triple Axel".to_string(),
119        "4A" => "Quad Axel".to_string(),
120
121        // Spin types
122        "USp" => "Upright Spin".to_string(),
123        "LSp" => "Layback Spin".to_string(),
124        "CSp" => "Camel Spin".to_string(),
125        "SSp" => "Sit Spin".to_string(),
126        "FUSp" => "Flying Upright Spin".to_string(),
127        "FLSp" => "Flying Layback Spin".to_string(),
128        "FCSp" => "Flying Camel Spin".to_string(),
129        "FSSp" => "Flying Sit Spin".to_string(),
130        "FCCSp" => "Flying Change Foot Camel Spin".to_string(),
131
132        // Step sequences
133        "StSq" => "Step Sequence".to_string(),
134        "ChSq" => "Choreographic Sequence".to_string(),
135
136        // Death spirals
137        "FiDs" => "Forward Inside Death Spiral".to_string(),
138        "BiDs" => "Backward Inside Death Spiral".to_string(),
139        "FoDs" => "Forward Outside Death Spiral".to_string(),
140        "BoDs" => "Backward Outside Death Spiral".to_string(),
141
142        // Pair spins
143        "PSp" => "Pair Spin".to_string(),
144        "PCoSp" => "Pair Combination Spin".to_string(),
145
146        // Twizzles
147        "STw" => "Twizzle".to_string(),
148
149        // Choreographic elements
150        "ChLi1" => "Choreographic Lift".to_string(),
151        "ChSp1" => "Choreographic Spinning Movement".to_string(),
152
153        _ => "Unknown Element".to_string(),
154    }
155}
156
157/// Returns the base cost of an element by its code
158fn get_base_value(code: &str) -> f32 {
159    match code {
160        // Jump base values
161        "1T" => 0.4,
162        "2T" => 1.3,
163        "3T" => 4.2,
164        "4T" => 9.5,
165        "1S" => 0.4,
166        "2S" => 1.3,
167        "3S" => 4.3,
168        "4S" => 9.7,
169        "1Lo" => 0.5,
170        "2Lo" => 1.7,
171        "3Lo" => 4.9,
172        "4Lo" => 10.5,
173        "1F" => 0.5,
174        "2F" => 1.8,
175        "3F" => 5.3,
176        "4F" => 11.0,
177        "1Lz" => 0.6,
178        "2Lz" => 2.1,
179        "3Lz" => 6.0,
180        "4Lz" => 11.5,
181        "1A" => 1.1,
182        "2A" => 3.3,
183        "3A" => 8.0,
184        "4A" => 12.5,
185
186        // Spin base values
187        "USp" => 1.2,
188        "LSp" => 1.5,
189        "CSp" => 1.7,
190        "SSp" => 1.8,
191        "FUSp" => 2.0,
192        "FLSp" => 2.3,
193        "FCSp" => 2.5,
194        "FSSp" => 2.6,
195        "FCCSp" => 2.8,
196
197        // Step sequences
198        "StSq" => 1.5,
199        "ChSq" => 3.0,
200
201        // Death spirals
202        "FiDs" => 1.5,
203        "BiDs" => 1.6,
204        "FoDs" => 1.7,
205        "BoDs" => 1.8,
206
207        // Pair spins
208        "PSp" => 1.7,
209        "PCoSp" => 2.5,
210
211        // Twizzles
212        "STw" => 1.0,
213
214        // Choreographic elements
215        "ChLi1" => 1.1,
216        "ChSp1" => 1.1,
217
218        _ => 0.0,
219    }
220}
221
222pub fn parse_elements(text: &str) -> Result<Vec<ParsedElement>, ParseError> {
223    ParsedElement::from_text(text)
224}