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 {:?}",
320 context
321 )
322 }
323 InvalidNodeError::InvalidProperty(context) => {
324 write!(f, "Invalid property: {}", context)
325 }
326 }
327 }
328}
329
330impl std::error::Error for InvalidNodeError {}
331
332#[cfg(test)]
333mod tests {
334 use super::InvalidNodeError;
335 use crate::go::parse;
336
337 #[test]
338 fn validate_sample_sgf_valid() {
339 let mut sgf_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
340 sgf_path.push("resources/test/ff4_ex.sgf");
341 let sgf = std::fs::read_to_string(sgf_path).unwrap();
342 let node = &parse(&sgf).unwrap()[0];
343 assert!(node.validate().is_ok());
344 }
345
346 #[test]
347 fn validate_valid_node() {
348 let sgf = "(;SZ[9]HA[3]C[Some comment];B[de];W[fe])";
349 let node = &parse(sgf).unwrap()[0];
350 assert!(node.validate().is_ok());
351 }
352
353 #[test]
354 fn validate_unexpected_root_properties() {
355 let sgf = "(;SZ[9]C[Some comment];GM[1])";
356 let node = &parse(sgf).unwrap()[0];
357 assert!(matches!(
358 node.validate(),
359 Err(InvalidNodeError::UnexpectedRootProperties(_))
360 ));
361 }
362
363 #[test]
364 fn validate_unexpected_game_info() {
365 let sgf = "(;SZ[9]KM[3.5]C[Some comment];HA[3])";
366 let node = &parse(sgf).unwrap()[0];
367 assert!(matches!(
368 node.validate(),
369 Err(InvalidNodeError::UnexpectedGameInfo(_))
370 ));
371 }
372
373 #[test]
374 fn validate_repeated_markup() {
375 let sgf = "(;SZ[9]KM[3.5]C[Some comment];CR[dd]TR[dd])";
376 let node = &parse(sgf).unwrap()[0];
377 assert!(matches!(
378 node.validate(),
379 Err(InvalidNodeError::RepeatedMarkup(_))
380 ));
381 }
382
383 #[test]
384 fn validate_multiple_moves() {
385 let sgf = "(;SZ[9]C[Some comment];B[dd]W[cd])";
386 let node = &parse(sgf).unwrap()[0];
387 assert!(matches!(
388 node.validate(),
389 Err(InvalidNodeError::MultipleMoves(_))
390 ));
391 }
392
393 #[test]
394 fn validate_repeated_identifier() {
395 let sgf = "(;SZ[9]HA[3]HA[4])";
396 let node = &parse(sgf).unwrap()[0];
397 assert!(matches!(
398 node.validate(),
399 Err(InvalidNodeError::RepeatedIdentifier(_))
400 ));
401 }
402
403 #[test]
404 fn validate_setup_and_move() {
405 let sgf = "(;AB[dd]B[cc])";
406 let node = &parse(sgf).unwrap()[0];
407 assert!(matches!(
408 node.validate(),
409 Err(InvalidNodeError::SetupAndMove(_))
410 ));
411 }
412
413 #[test]
414 fn validate_ko_without_move() {
415 let sgf = "(;KO[])";
416 let node = &parse(sgf).unwrap()[0];
417 assert!(matches!(
418 node.validate(),
419 Err(InvalidNodeError::KoWithoutMove(_))
420 ));
421 }
422
423 #[test]
424 fn validate_multiple_move_annotations() {
425 let sgf = "(;B[dd]DO[]BM[1])";
426 let node = &parse(sgf).unwrap()[0];
427 assert!(matches!(
428 node.validate(),
429 Err(InvalidNodeError::MultipleMoveAnnotations(_))
430 ));
431 }
432
433 #[test]
434 fn validate_unexpected_move_annotation() {
435 let sgf = "(;BM[1])";
436 let node = &parse(sgf).unwrap()[0];
437 assert!(matches!(
438 node.validate(),
439 Err(InvalidNodeError::UnexpectedMoveAnnotation(_))
440 ));
441 }
442
443 #[test]
444 fn validate_multiple_exclusive_annotations() {
445 let sgf = "(;UC[2]GW[2])";
446 let node = &parse(sgf).unwrap()[0];
447 assert!(matches!(
448 node.validate(),
449 Err(InvalidNodeError::MultipleExclusiveAnnotations(_))
450 ));
451 }
452
453 #[test]
454 fn validate_invalid_property() {
455 let sgf = "(;BM[Invalid])";
456 let node = &parse(sgf).unwrap()[0];
457 assert!(matches!(
458 node.validate(),
459 Err(InvalidNodeError::InvalidProperty(_))
460 ));
461 }
462}