1use std::ptr::NonNull;
2
3use crate::go;
4use crate::lexer::{tokenize, LexerError, Token};
5use crate::unknown_game;
6use crate::{GameTree, GameType, SgfNode, SgfProp};
7
8pub fn parse(text: &str) -> Result<Vec<GameTree>, SgfParseError> {
26 parse_with_options(text, &ParseOptions::default())
27}
28
29pub fn parse_with_options(
44 text: &str,
45 options: &ParseOptions,
46) -> Result<Vec<GameTree>, SgfParseError> {
47 let tokens = tokenize(text)
48 .map(|result| match result {
49 Err(e) => Err(SgfParseError::LexerError(e)),
50 Ok((token, _span)) => Ok(token),
51 })
52 .collect::<Result<Vec<_>, _>>()?;
53 split_by_gametree(&tokens)?
54 .into_iter()
55 .map(|tokens| match find_gametype(tokens)? {
56 GameType::Go => parse_gametree::<go::Prop>(tokens, options),
57 GameType::Unknown => parse_gametree::<unknown_game::Prop>(tokens, options),
58 })
59 .collect::<Result<_, _>>()
60}
61
62pub struct ParseOptions {
64 pub convert_mixed_case_identifiers: bool,
69}
70
71impl Default for ParseOptions {
72 fn default() -> Self {
73 ParseOptions {
74 convert_mixed_case_identifiers: true,
75 }
76 }
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81pub enum SgfParseError {
82 LexerError(LexerError),
83 UnexpectedGameTreeStart,
84 UnexpectedGameTreeEnd,
85 UnexpectedProperty,
86 UnexpectedEndOfData,
87 UnexpectedGameType,
88 InvalidFF4Property,
89}
90
91impl From<LexerError> for SgfParseError {
92 fn from(error: LexerError) -> Self {
93 Self::LexerError(error)
94 }
95}
96
97impl std::fmt::Display for SgfParseError {
98 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
99 match self {
100 SgfParseError::LexerError(e) => write!(f, "Error tokenizing: {}", e),
101 SgfParseError::UnexpectedGameTreeStart => write!(f, "Unexpected start of game tree"),
102 SgfParseError::UnexpectedGameTreeEnd => write!(f, "Unexpected end of game tree"),
103 SgfParseError::UnexpectedProperty => write!(f, "Unexpected property"),
104 SgfParseError::UnexpectedEndOfData => write!(f, "Unexpected end of data"),
105 SgfParseError::UnexpectedGameType => write!(f, "Unexpected game type"),
106 SgfParseError::InvalidFF4Property => {
107 write!(
108 f,
109 "Invalid FF[4] property without `convert_mixed_case_identifiers`"
110 )
111 }
112 }
113 }
114}
115
116impl std::error::Error for SgfParseError {}
117
118fn split_by_gametree(tokens: &[Token]) -> Result<Vec<&[Token]>, SgfParseError> {
123 let mut gametrees = vec![];
124 let mut gametree_depth: u64 = 0;
125 let mut slice_start = 0;
126 for (i, token) in tokens.iter().enumerate() {
127 match token {
128 Token::StartGameTree => gametree_depth += 1,
129 Token::EndGameTree => {
130 if gametree_depth == 0 {
131 return Err(SgfParseError::UnexpectedGameTreeEnd);
132 }
133 gametree_depth -= 1;
134 if gametree_depth == 0 {
135 gametrees.push(&tokens[slice_start..=i]);
136 slice_start = i + 1;
137 }
138 }
139 _ => {}
140 }
141 }
142 if gametree_depth != 0 {
143 return Err(SgfParseError::UnexpectedEndOfData);
144 }
145
146 Ok(gametrees)
147}
148
149fn parse_gametree<Prop: SgfProp>(
151 tokens: &[Token],
152 options: &ParseOptions,
153) -> Result<GameTree, SgfParseError>
154where
155 SgfNode<Prop>: std::convert::Into<GameTree>,
156{
157 let mut collection: Vec<SgfNode<Prop>> = vec![];
159 let mut current_node_list_ptr = NonNull::new(&mut collection).unwrap();
161 let mut incomplete_child_lists: Vec<NonNull<Vec<SgfNode<Prop>>>> = vec![];
163 let mut tokens = tokens.iter().peekable();
172 while let Some(token) = tokens.next() {
173 match token {
174 Token::StartGameTree => {
175 if let Some(node_list_ptr) = incomplete_child_lists.last() {
177 let node_list = unsafe { node_list_ptr.as_ref() };
178 if node_list.is_empty() {
179 return Err(SgfParseError::UnexpectedGameTreeStart);
180 }
181 }
182 incomplete_child_lists.push(current_node_list_ptr);
183 }
184 Token::EndGameTree => match incomplete_child_lists.pop() {
185 Some(node_list) => current_node_list_ptr = node_list,
186 None => return Err(SgfParseError::UnexpectedGameTreeEnd),
187 },
188 Token::StartNode => {
189 let mut new_node = SgfNode::default();
190 let mut prop_tokens = vec![];
191 while let Some(Token::Property(_)) = tokens.peek() {
192 prop_tokens.push(tokens.next().unwrap());
193 }
194 for token in prop_tokens {
195 match token {
196 Token::Property((identifier, values)) => {
198 let identifier = {
199 if identifier.chars().all(|c| c.is_ascii_uppercase()) {
200 identifier.clone()
201 } else if options.convert_mixed_case_identifiers {
202 identifier
203 .chars()
204 .filter(|c| c.is_ascii_uppercase())
205 .collect()
206 } else {
207 return Err(SgfParseError::InvalidFF4Property);
208 }
209 };
210 new_node
211 .properties
212 .push(Prop::new(identifier, values.clone()))
213 }
214 _ => unreachable!(),
215 }
216 }
217 let node_list = unsafe { current_node_list_ptr.as_mut() };
218 node_list.push(new_node);
219 current_node_list_ptr =
220 NonNull::new(&mut node_list.last_mut().unwrap().children).unwrap();
221 }
222 Token::Property(_) => return Err(SgfParseError::UnexpectedProperty),
223 }
224 }
225
226 if !incomplete_child_lists.is_empty() || collection.len() != 1 {
227 return Err(SgfParseError::UnexpectedEndOfData);
228 }
229 let mut root_node = collection.into_iter().next().unwrap();
230 root_node.is_root = true;
231 Ok(root_node.into())
232}
233
234fn find_gametype(tokens: &[Token]) -> Result<GameType, SgfParseError> {
238 match find_gametree_root_prop_values("GM", tokens)? {
239 None => Ok(GameType::Go),
240 Some(values) => {
241 if values.len() != 1 {
242 return Ok(GameType::Unknown);
243 }
244 match values[0].as_str() {
245 "1" => Ok(GameType::Go),
246 _ => Ok(GameType::Unknown),
247 }
248 }
249 }
250}
251
252fn find_gametree_root_prop_values<'a>(
257 prop_ident: &'a str,
258 tokens: &'a [Token],
259) -> Result<Option<&'a Vec<String>>, SgfParseError> {
260 let matching_tokens: Vec<&Vec<String>> = tokens
263 .iter()
264 .skip(2)
265 .take_while(|&token| matches!(token, Token::Property(_)))
266 .filter_map(move |token| match token {
267 Token::Property((ident, values)) if ident == prop_ident => Some(values),
268 _ => None,
269 })
270 .collect();
271
272 match matching_tokens.len() {
273 0 => Ok(None),
274 1 => Ok(Some(matching_tokens[0])),
275 _ => Err(SgfParseError::UnexpectedProperty),
276 }
277}
278
279#[cfg(test)]
280mod test {
281 use super::*;
282 use crate::{go, serialize};
283
284 fn load_test_sgf() -> Result<String, Box<dyn std::error::Error>> {
285 let mut sgf_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
287 sgf_path.push("resources/test/ff4_ex.sgf");
288 let data = std::fs::read_to_string(sgf_path)?;
289
290 Ok(data)
291 }
292
293 fn get_go_nodes() -> Result<Vec<SgfNode<go::Prop>>, Box<dyn std::error::Error>> {
294 let data = load_test_sgf()?;
295
296 Ok(go::parse(&data)?)
297 }
298
299 fn node_depth(mut sgf_node: &SgfNode<go::Prop>) -> u64 {
300 let mut depth = 1;
301 while sgf_node.children().count() > 0 {
302 depth += 1;
303 sgf_node = sgf_node.children().next().unwrap();
304 }
305 depth
306 }
307
308 #[test]
309 fn sgf_has_two_gametrees() {
310 let sgf_nodes = get_go_nodes().unwrap();
311 assert_eq!(sgf_nodes.len(), 2);
312 }
313
314 #[test]
315 fn gametree_one_has_five_variations() {
316 let sgf_nodes = get_go_nodes().unwrap();
317 let sgf_node = &sgf_nodes[0];
318 assert_eq!(sgf_node.children().count(), 5);
319 }
320
321 #[test]
322 fn gametree_one_has_size_19() {
323 let sgf_nodes = get_go_nodes().unwrap();
324 let sgf_node = &sgf_nodes[0];
325 match sgf_node.get_property("SZ") {
326 Some(go::Prop::SZ(size)) => assert_eq!(size, &(19, 19)),
327 _ => unreachable!("Expected size property"),
328 }
329 }
330
331 #[test]
332 fn gametree_variation_depths() {
333 let sgf_nodes = get_go_nodes().unwrap();
334 let sgf_node = &sgf_nodes[0];
335 let children: Vec<_> = sgf_node.children().collect();
336 assert_eq!(node_depth(children[0]), 13);
337 assert_eq!(node_depth(children[1]), 4);
338 assert_eq!(node_depth(children[2]), 4);
339 }
340
341 #[test]
342 fn gametree_two_has_one_variation() {
343 let sgf_nodes = get_go_nodes().unwrap();
344 let sgf_node = &sgf_nodes[1];
345 assert_eq!(sgf_node.children().count(), 1);
346 }
347
348 #[test]
349 fn serialize_then_parse() {
350 let data = load_test_sgf().unwrap();
351 let gametrees = parse(&data).unwrap();
352 let text = serialize(&gametrees);
353 assert_eq!(gametrees, parse(&text).unwrap());
354 }
355
356 #[test]
357 fn invalid_property() {
358 let input = "(;GM[1]W[rp.pmonpoqprpsornqmpm])";
359 let sgf_nodes = go::parse(input).unwrap();
360 let expected = vec![
361 go::Prop::GM(1),
362 go::Prop::Invalid("W".to_string(), vec!["rp.pmonpoqprpsornqmpm".to_string()]),
363 ];
364
365 assert_eq!(sgf_nodes.len(), 1);
366 let sgf_node = &sgf_nodes[0];
367 assert_eq!(sgf_node.properties().cloned().collect::<Vec<_>>(), expected);
368 }
369
370 #[test]
371 fn unknown_game() {
372 let input = "(;GM[37]W[rp.pmonpoqprpsornqmpm])";
373 let gametrees = parse(input).unwrap();
374 assert_eq!(gametrees.len(), 1);
375 assert_eq!(gametrees[0].gametype(), GameType::Unknown);
376 let sgf_node = match &gametrees[0] {
377 GameTree::Unknown(node) => node,
378 _ => panic!("Unexpected game type"),
379 };
380 let expected = vec![
381 unknown_game::Prop::GM(37),
382 unknown_game::Prop::W("rp.pmonpoqprpsornqmpm".into()),
383 ];
384
385 assert_eq!(sgf_node.properties().cloned().collect::<Vec<_>>(), expected);
386 }
387
388 #[test]
389 fn mixed_games() {
390 let input = "(;GM[1];W[dd])(;GM[37]W[rp.pmonpoqprpsornqmpm])";
391 let gametrees = parse(input).unwrap();
392 assert_eq!(gametrees.len(), 2);
393 assert_eq!(gametrees[0].gametype(), GameType::Go);
394 assert_eq!(gametrees[1].gametype(), GameType::Unknown);
395 }
396
397 #[test]
398 fn stack_overflow() {
399 let input = "(;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;)";
401 let result = parse(&input);
402 assert!(result.is_ok());
403 }
404
405 #[test]
406 fn converts_up_ff3_property() {
407 let input = "(;GM[1]FF[3]CoPyright[test])";
408 let expected = vec![
409 go::Prop::GM(1),
410 go::Prop::FF(3),
411 go::Prop::CP("test".into()),
412 ];
413
414 let sgf_nodes = go::parse(input).unwrap();
415
416 assert_eq!(sgf_nodes.len(), 1);
417 let properties = sgf_nodes[0].properties().cloned().collect::<Vec<_>>();
418 assert_eq!(properties, expected);
419 }
420
421 #[test]
422 fn doesnt_convert_if_not_allowed() {
423 let input = "(;GM[1]FF[3]CoPyright[test])";
424 let parse_options = ParseOptions {
425 convert_mixed_case_identifiers: false,
426 ..ParseOptions::default()
427 };
428 let result = parse_with_options(input, &parse_options);
429 assert_eq!(result, Err(SgfParseError::InvalidFF4Property));
430 }
431
432 #[test]
433 fn compressed_list_for_unknown_game() {
434 let input = "(;GM[]MA[a:b])";
435 let gametree = parse(&input).unwrap().pop().unwrap();
436 let node = match gametree {
437 GameTree::Unknown(node) => node,
438 _ => panic!("Expected Unknown Game type"),
439 };
440 match node.get_property("MA") {
441 Some(unknown_game::Prop::MA(values)) => {
442 assert_eq!(values.len(), 1);
443 assert!(values.contains("a:b"));
444 }
445 _ => panic!("MA prop not found"),
446 }
447 }
448}