use std::collections::HashSet;
use super::{PropertyType, SgfParseError, SgfProp};
#[derive(Clone, Debug, PartialEq)]
pub struct SgfNode {
properties: Vec<SgfProp>,
children: Vec<SgfNode>,
is_root: bool,
has_game_info: bool,
}
impl SgfNode {
pub fn new(
properties: Vec<SgfProp>,
children: Vec<Self>,
is_root: bool,
) -> Result<Self, SgfParseError> {
let (has_root_props, has_game_info_props) = validate_node_props(&properties)?;
if has_root_props && !is_root {
return Err(SgfParseError::InvalidNode(
"Root properties in non-root node".to_string(),
));
}
let children_have_game_info = children.iter().any(|child| child.has_game_info);
if has_game_info_props && children_have_game_info {
return Err(SgfParseError::InvalidNode(
"Multiple GameInfo nodes in path.".to_string(),
));
}
Ok(Self {
properties,
children,
is_root,
has_game_info: has_game_info_props || children_have_game_info,
})
}
pub fn get_property(&self, identifier: &str) -> Option<&SgfProp> {
for prop in &self.properties {
if prop.identifier() == identifier {
return Some(prop);
}
}
None
}
pub fn children<'a>(&'a self) -> impl Iterator<Item = &Self> + 'a {
self.children.iter()
}
pub fn properties<'a>(&'a self) -> impl Iterator<Item = &SgfProp> + 'a {
self.properties.iter()
}
pub fn into_builder(self) -> SgfNodeBuilder {
let Self {
properties,
children,
is_root,
has_game_info: _,
} = self;
let children = children
.into_iter()
.map(Self::into_builder)
.collect::<Vec<_>>();
SgfNodeBuilder {
properties,
children,
is_root,
}
}
}
fn validate_node_props(props: &[SgfProp]) -> Result<(bool, bool), SgfParseError> {
let mut identifiers = HashSet::new();
let mut markup_points = HashSet::new();
let mut setup_node = false;
let mut move_node = false;
let mut move_seen = false;
let mut game_info_node = false;
let mut root_node = false;
let mut exclusive_node_annotations = 0;
let mut move_annotation_count = 0;
for prop in props.iter() {
match prop {
SgfProp::B(_) => {
move_seen = true;
if identifiers.contains("W") {
return Err(SgfParseError::InvalidNodeProps(props.to_owned()));
}
}
SgfProp::W(_) => {
move_seen = true;
if identifiers.contains("B") {
return Err(SgfParseError::InvalidNodeProps(props.to_owned()));
}
}
SgfProp::CR(ps)
| SgfProp::MA(ps)
| SgfProp::SL(ps)
| SgfProp::SQ(ps)
| SgfProp::TR(ps) => {
for p in ps.iter() {
if markup_points.contains(&p) {
return Err(SgfParseError::InvalidNodeProps(props.to_owned()));
}
markup_points.insert(p);
}
}
SgfProp::DM(_) | SgfProp::UC(_) | SgfProp::GW(_) | SgfProp::GB(_) => {
exclusive_node_annotations += 1
}
SgfProp::BM(_) | SgfProp::DO | SgfProp::IT | SgfProp::TE(_) => {
move_annotation_count += 1
}
_ => {}
}
match prop.property_type() {
Some(PropertyType::Move) => move_node = true,
Some(PropertyType::Setup) => setup_node = true,
Some(PropertyType::GameInfo) => game_info_node = true,
Some(PropertyType::Root) => root_node = true,
_ => {}
}
let ident = prop.identifier();
if identifiers.contains(&ident) {
return Err(SgfParseError::InvalidNodeProps(props.to_owned()));
}
identifiers.insert(prop.identifier());
}
if setup_node && move_node {
return Err(SgfParseError::InvalidNodeProps(props.to_owned()));
}
if identifiers.contains("KO") && !(identifiers.contains("B") || identifiers.contains("W")) {
return Err(SgfParseError::InvalidNodeProps(props.to_owned()));
}
if move_annotation_count > 1 || (move_annotation_count == 1 && !move_seen) {
return Err(SgfParseError::InvalidNodeProps(props.to_owned()));
}
if exclusive_node_annotations > 1 {
return Err(SgfParseError::InvalidNodeProps(props.to_owned()));
}
Ok((root_node, game_info_node))
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct SgfNodeBuilder {
pub properties: Vec<SgfProp>,
pub children: Vec<SgfNodeBuilder>,
pub is_root: bool,
}
impl SgfNodeBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn build(self) -> Result<SgfNode, SgfParseError> {
use core::cell::RefCell;
use std::rc::Rc;
let mut node_parts = vec![];
let mut dfs_stack = vec![(self, None)];
while let Some((node, parent_children)) = dfs_stack.pop() {
let Self {
properties,
children,
is_root,
} = node;
let built_children: Rc<RefCell<Vec<SgfNode>>> = Rc::new(RefCell::new(vec![]));
for child in children {
dfs_stack.push((child, Some(built_children.clone())));
}
node_parts.push((properties, built_children, is_root, parent_children));
}
for (properties, children, is_root, parent_children) in node_parts.into_iter().rev() {
let children = Rc::try_unwrap(children)
.expect("All children should already be built")
.into_inner();
let new_node = SgfNode::new(properties, children, is_root)?;
if let Some(parent_children) = parent_children {
parent_children.borrow_mut().push(new_node);
} else {
return Ok(new_node);
}
}
unreachable!("The first node must have no parent")
}
}