1use crate::props::{PropertyType, SgfProp};
2
3#[derive(Clone, Debug, PartialEq)]
10pub struct SgfNode<Prop: SgfProp> {
11 pub properties: Vec<Prop>,
12 pub children: Vec<Self>,
13 pub is_root: bool,
14}
15
16impl<Prop: SgfProp> Default for SgfNode<Prop> {
17 fn default() -> Self {
18 Self {
19 properties: vec![],
20 children: vec![],
21 is_root: false,
22 }
23 }
24}
25
26impl<Prop: SgfProp> SgfNode<Prop> {
27 pub fn new(properties: Vec<Prop>, children: Vec<Self>, is_root: bool) -> Self {
44 Self {
45 properties,
46 children,
47 is_root,
48 }
49 }
50
51 pub fn get_property(&self, identifier: &str) -> Option<&Prop> {
65 self.properties
66 .iter()
67 .find(|&prop| prop.identifier() == identifier)
68 }
69
70 pub fn children(&self) -> impl Iterator<Item = &Self> {
84 self.children.iter()
85 }
86
87 pub fn properties(&self) -> impl Iterator<Item = &Prop> {
109 self.properties.iter()
110 }
111
112 pub fn serialize(&self) -> String {
123 format!("({self})")
124 }
125
126 pub fn validate(&self) -> Result<(), InvalidNodeError> {
141 self.validate_helper()?;
143 Ok(())
144 }
145
146 fn validate_helper(&self) -> Result<bool, InvalidNodeError> {
148 Prop::validate_properties(&self.properties, self.is_root)?;
149 let has_game_info = self.has_game_info();
150 let mut child_has_game_info = false;
151 for child in self.children() {
152 child_has_game_info |= child.validate_helper()?;
153 }
154 if child_has_game_info && has_game_info {
155 return Err(InvalidNodeError::UnexpectedGameInfo(format!(
156 "{:?}",
157 self.properties
158 )));
159 }
160 Ok(has_game_info)
161 }
162
163 pub fn main_variation(&self) -> impl Iterator<Item = &Self> {
195 MainVariationIter {
196 node: Some(self),
197 started: false,
198 }
199 }
200
201 pub fn get_move(&self) -> Option<&Prop> {
214 self.properties()
216 .find(|p| p.property_type() == Some(PropertyType::Move))
217 }
218
219 fn has_game_info(&self) -> bool {
220 for prop in self.properties() {
221 if let Some(PropertyType::GameInfo) = prop.property_type() {
222 return true;
223 }
224 }
225 false
226 }
227}
228
229impl<Prop: SgfProp> std::fmt::Display for SgfNode<Prop> {
230 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
231 let prop_string = self
233 .properties()
234 .map(|x| x.to_string())
235 .collect::<Vec<_>>()
236 .join("");
237 let child_count = self.children().count();
238 let child_string = match child_count {
239 0 => "".to_string(),
240 1 => self.children().next().unwrap().to_string(),
241 _ => self
242 .children()
243 .map(|x| format!("({x})"))
244 .collect::<Vec<_>>()
245 .join(""),
246 };
247 write!(f, ";{prop_string}{child_string}")
248 }
249}
250
251#[derive(Debug)]
252struct MainVariationIter<'a, Prop: SgfProp> {
253 node: Option<&'a SgfNode<Prop>>,
254 started: bool,
255}
256
257impl<'a, Prop: SgfProp> Iterator for MainVariationIter<'a, Prop> {
258 type Item = &'a SgfNode<Prop>;
259
260 fn next(&mut self) -> Option<Self::Item> {
261 if self.started {
262 self.node = self.node.and_then(|n| n.children().next());
263 } else {
264 self.started = true;
265 }
266 self.node
267 }
268}
269
270#[derive(Debug, Clone, PartialEq, Eq)]
272pub enum InvalidNodeError {
273 UnexpectedRootProperties(String),
274 UnexpectedGameInfo(String),
275 RepeatedMarkup(String),
276 MultipleMoves(String),
277 RepeatedIdentifier(String),
278 SetupAndMove(String),
279 KoWithoutMove(String),
280 MultipleMoveAnnotations(String),
281 UnexpectedMoveAnnotation(String),
282 MultipleExclusiveAnnotations(String),
283 InvalidProperty(String),
284}
285
286impl std::fmt::Display for InvalidNodeError {
287 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
288 match self {
289 InvalidNodeError::UnexpectedRootProperties(context) => {
290 write!(f, "Root properties in non-root node: {context:?}")
291 }
292 InvalidNodeError::UnexpectedGameInfo(context) => {
293 write!(f, "GameInfo properties in node and a child {context:?}")
294 }
295 InvalidNodeError::RepeatedMarkup(context) => {
296 write!(f, "Multiple markup properties on same point {context:?}")
297 }
298 InvalidNodeError::MultipleMoves(context) => {
299 write!(f, "B and W moves in same node {context:?}")
300 }
301 InvalidNodeError::RepeatedIdentifier(context) => {
302 write!(f, "Identifier repeated in node {context:?}")
303 }
304 InvalidNodeError::SetupAndMove(context) => {
305 write!(f, "Setup and move properties in same node {context:?}")
306 }
307 InvalidNodeError::KoWithoutMove(context) => {
308 write!(f, "Ko in node without B or W {context:?}")
309 }
310 InvalidNodeError::MultipleMoveAnnotations(context) => {
311 write!(f, "Multiple move annotations in same node {context:?}")
312 }
313 InvalidNodeError::UnexpectedMoveAnnotation(context) => {
314 write!(f, "Move annotation without move in node {context:?}")
315 }
316 InvalidNodeError::MultipleExclusiveAnnotations(context) => {
317 write!(
318 f,
319 "Multiple DM, UC, GW or GB properties in node {context:?}"
320 )
321 }
322 InvalidNodeError::InvalidProperty(context) => {
323 write!(f, "Invalid property: {context:?}")
324 }
325 }
326 }
327}
328
329impl std::error::Error for InvalidNodeError {}
330
331#[cfg(test)]
332mod tests {
333 use super::InvalidNodeError;
334 use crate::go::parse;
335
336 #[test]
337 fn validate_sample_sgf_valid() {
338 let mut sgf_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
339 sgf_path.push("resources/test/ff4_ex.sgf");
340 let sgf = std::fs::read_to_string(sgf_path).unwrap();
341 let node = &parse(&sgf).unwrap()[0];
342 assert!(node.validate().is_ok());
343 }
344
345 #[test]
346 fn validate_valid_node() {
347 let sgf = "(;SZ[9]HA[3]C[Some comment];B[de];W[fe])";
348 let node = &parse(sgf).unwrap()[0];
349 assert!(node.validate().is_ok());
350 }
351
352 #[test]
353 fn validate_unexpected_root_properties() {
354 let sgf = "(;SZ[9]C[Some comment];GM[1])";
355 let node = &parse(sgf).unwrap()[0];
356 assert!(matches!(
357 node.validate(),
358 Err(InvalidNodeError::UnexpectedRootProperties(_))
359 ));
360 }
361
362 #[test]
363 fn validate_unexpected_game_info() {
364 let sgf = "(;SZ[9]KM[3.5]C[Some comment];HA[3])";
365 let node = &parse(sgf).unwrap()[0];
366 assert!(matches!(
367 node.validate(),
368 Err(InvalidNodeError::UnexpectedGameInfo(_))
369 ));
370 }
371
372 #[test]
373 fn validate_repeated_markup() {
374 let sgf = "(;SZ[9]KM[3.5]C[Some comment];CR[dd]TR[dd])";
375 let node = &parse(sgf).unwrap()[0];
376 assert!(matches!(
377 node.validate(),
378 Err(InvalidNodeError::RepeatedMarkup(_))
379 ));
380 }
381
382 #[test]
383 fn validate_multiple_moves() {
384 let sgf = "(;SZ[9]C[Some comment];B[dd]W[cd])";
385 let node = &parse(sgf).unwrap()[0];
386 assert!(matches!(
387 node.validate(),
388 Err(InvalidNodeError::MultipleMoves(_))
389 ));
390 }
391
392 #[test]
393 fn validate_repeated_identifier() {
394 let sgf = "(;SZ[9]HA[3]HA[4])";
395 let node = &parse(sgf).unwrap()[0];
396 assert!(matches!(
397 node.validate(),
398 Err(InvalidNodeError::RepeatedIdentifier(_))
399 ));
400 }
401
402 #[test]
403 fn validate_setup_and_move() {
404 let sgf = "(;AB[dd]B[cc])";
405 let node = &parse(sgf).unwrap()[0];
406 assert!(matches!(
407 node.validate(),
408 Err(InvalidNodeError::SetupAndMove(_))
409 ));
410 }
411
412 #[test]
413 fn validate_ko_without_move() {
414 let sgf = "(;KO[])";
415 let node = &parse(sgf).unwrap()[0];
416 assert!(matches!(
417 node.validate(),
418 Err(InvalidNodeError::KoWithoutMove(_))
419 ));
420 }
421
422 #[test]
423 fn validate_multiple_move_annotations() {
424 let sgf = "(;B[dd]DO[]BM[1])";
425 let node = &parse(sgf).unwrap()[0];
426 assert!(matches!(
427 node.validate(),
428 Err(InvalidNodeError::MultipleMoveAnnotations(_))
429 ));
430 }
431
432 #[test]
433 fn validate_unexpected_move_annotation() {
434 let sgf = "(;BM[1])";
435 let node = &parse(sgf).unwrap()[0];
436 assert!(matches!(
437 node.validate(),
438 Err(InvalidNodeError::UnexpectedMoveAnnotation(_))
439 ));
440 }
441
442 #[test]
443 fn validate_multiple_exclusive_annotations() {
444 let sgf = "(;UC[2]GW[2])";
445 let node = &parse(sgf).unwrap()[0];
446 assert!(matches!(
447 node.validate(),
448 Err(InvalidNodeError::MultipleExclusiveAnnotations(_))
449 ));
450 }
451
452 #[test]
453 fn validate_invalid_property() {
454 let sgf = "(;BM[Invalid])";
455 let node = &parse(sgf).unwrap()[0];
456 assert!(matches!(
457 node.validate(),
458 Err(InvalidNodeError::InvalidProperty(_))
459 ));
460 }
461}