1use pest::error::ErrorVariant;
2use pest::iterators::Pair;
3use pest::{Parser, Span};
4use pest_derive::Parser;
5
6#[derive(Parser)]
7#[grammar = "pgn.pest"]
8pub struct PGNParser;
9
10pub struct ParsedGame {
11 pub metadata: Vec<(String, String)>,
12 pub moves: Vec<(Move, Option<Move>)>,
13 pub game_result: String,
14}
15
16impl ToString for ParsedGame {
17 fn to_string(&self) -> String {
18 let metadata_str: String = self
19 .metadata
20 .iter()
21 .map(|(key, value)| format!("{}: {}", key, value))
22 .collect::<Vec<String>>()
23 .join("\n");
24
25 let moves_str: String = self
26 .moves
27 .iter()
28 .enumerate()
29 .map(|(index, (first_move, second_move))| {
30 let first_move_str = first_move.to_string();
31 let second_move_str = second_move
32 .as_ref()
33 .map(|mv| mv.to_string())
34 .unwrap_or_default();
35
36 format!(
37 "Move #{}\nWhite: {} Black: {}",
38 (index + 1),
39 first_move_str,
40 second_move_str
41 )
42 })
43 .collect::<Vec<String>>()
44 .join("\n");
45
46 format!(
47 "Game Metadata\n{}\n\nList of moves\n{}\n\nGame Result\n{}\n",
48 metadata_str, moves_str, self.game_result
49 )
50 }
51}
52
53pub struct Move {
54 piece_move: String,
55 comment: Option<String>,
56}
57
58impl ToString for Move {
59 fn to_string(&self) -> String {
60 let piece_move_str = &self.piece_move;
61 let comment_str = self.comment.as_ref().map_or("", |comment| comment);
62 format!("{} {}", piece_move_str, comment_str)
63 }
64}
65
66pub fn extract_pairs(pair: Pair<Rule>, rule: Rule) -> Option<Pair<Rule>> {
67 pair.into_inner().find(|pair| pair.as_rule() == rule)
68}
69
70pub fn parse_pgn(to_parse: &str) -> Result<ParsedGame, pest::error::Error<Rule>> {
71 let mut parsed_game = ParsedGame {
72 metadata: Vec::new(),
73 moves: Vec::new(),
74 game_result: String::new(),
75 };
76 let parse_result = PGNParser::parse(Rule::game, to_parse);
77
78 return match parse_result {
79 Ok(pairs) => {
80 let pgn_game = pairs
81 .into_iter()
82 .find(|pair| pair.as_rule() == Rule::game)
83 .ok_or_else(|| {
84 pest::error::Error::<Rule>::new_from_span(
85 ErrorVariant::CustomError {
86 message: String::from("Game wasn't found"),
87 },
88 Span::new(to_parse, 0, to_parse.len()).unwrap(),
89 )
90 })?;
91 let move_list = extract_pairs(pgn_game.clone(), Rule::move_list).ok_or_else(|| {
92 pest::error::Error::<Rule>::new_from_span(
93 ErrorVariant::CustomError {
94 message: String::from("Move list wasn't found"),
95 },
96 Span::new(to_parse, 0, to_parse.len()).unwrap(),
97 )
98 })?;
99 let metadata_block: Vec<(String, String)> = pgn_game
100 .clone()
101 .into_inner()
102 .filter_map(|pair| match pair.as_rule() {
103 Rule::metadata_block => {
104 let mut inner_pairs = pair.into_inner();
105 let key = inner_pairs.next().unwrap().as_str().to_string();
106 let value = inner_pairs.next().unwrap().as_str().to_string();
107 Some((key, value))
108 }
109 _ => None,
110 })
111 .collect();
112 let game_result =
113 extract_pairs(pgn_game.clone(), Rule::game_result).ok_or_else(|| {
114 pest::error::Error::<Rule>::new_from_span(
115 ErrorVariant::CustomError {
116 message: String::from("Game result wasn't found"),
117 },
118 Span::new(to_parse, 0, to_parse.len()).unwrap(),
119 )
120 })?;
121
122 let moves_list: Vec<(Move, Option<Move>)> = move_list
123 .clone()
124 .into_inner()
125 .filter_map(|pair| match pair.as_rule() {
126 Rule::move_pair => {
127 let mut first_comment: Option<String> = None;
128 let mut second_comment: Option<String> = None;
129 let mut second_piece_move: Option<String> = None;
130 let mut inner_pairs = pair.into_inner();
131 let first_piece_move = inner_pairs.next().unwrap().as_str().to_string();
132 let second_lex = inner_pairs.next();
133 let third_lex = inner_pairs.next();
134 let fourth_lex = inner_pairs.next();
135 if let Some(pair) = second_lex {
136 if pair.as_rule() == Rule::move_comment {
137 first_comment = Some(pair.as_str().to_string());
138 } else {
139 second_piece_move = Some(pair.as_str().to_string());
140 }
141 }
142 if let Some(pair) = third_lex {
143 if pair.as_rule() == Rule::move_comment {
144 second_comment = Some(pair.as_str().to_string());
145 } else {
146 second_piece_move = Some(pair.as_str().to_string());
147 }
148 }
149
150 if let Some(pair) = fourth_lex {
151 second_comment = Some(pair.as_str().to_string());
152 }
153
154 let first_move = Move {
155 piece_move: first_piece_move,
156 comment: first_comment,
157 };
158 if let Some(piece_move) = second_piece_move {
159 let second_move = Move {
160 piece_move,
161 comment: second_comment,
162 };
163 Some((first_move, Some(second_move)))
164 } else {
165 Some((first_move, None))
166 }
167 }
168 _ => None,
169 })
170 .collect();
171 parsed_game.game_result = game_result.as_str().to_string();
172 parsed_game.moves = moves_list;
173 parsed_game.metadata = metadata_block;
174 return Ok(parsed_game);
175 }
176 Err(e) => Err(e),
177 };
178}