sgf_parser/
parser.rs

1use pest::Parser;
2
3use pest::iterators::Pair;
4use pest_derive::*;
5
6use crate::*;
7
8#[derive(Parser)]
9#[grammar = "../sgf.pest"]
10struct SGFParser;
11
12///
13/// Main entry point to the library. Parses an SGF string, and returns a `GameTree`.
14///
15/// Returns an `SgfError` when parsing failed, but it tries to recover from most kind of invalid input and insert `SgfToken::Invalid` or `SgfToken::Unknown` rather than failing
16///
17/// ```rust
18/// use sgf_parser::*;
19///
20/// let tree: Result<GameTree, SgfError> = parse("(;EV[event]PB[black]PW[white]C[comment];B[aa];W[bb])");
21///
22/// let tree = tree.unwrap();
23/// assert_eq!(tree.count_max_nodes(), 3);
24/// ```
25///
26pub fn parse(input: &str) -> Result<GameTree, SgfError> {
27    let mut parse_roots =
28        SGFParser::parse(Rule::game_tree, input).map_err(SgfError::parse_error)?;
29    if let Some(game_tree) = parse_roots.next() {
30        let tree = parse_pair(game_tree);
31        let game = create_game_tree(tree, true)?;
32        Ok(game)
33    } else {
34        Ok(GameTree::default())
35    }
36}
37
38/// Creates a `GameTree` from the Pest result
39fn create_game_tree(parser_node: ParserNode<'_>, is_root: bool) -> Result<GameTree, SgfError> {
40    if let ParserNode::GameTree(tree_nodes) = parser_node {
41        let mut nodes: Vec<GameNode> = vec![];
42        let mut variations: Vec<GameTree> = vec![];
43        for node in tree_nodes {
44            match node {
45                ParserNode::Sequence(sequence_nodes) => {
46                    nodes.extend(parse_sequence(sequence_nodes)?)
47                }
48                ParserNode::GameTree(_) => {
49                    variations.push(create_game_tree(node, false)?);
50                }
51                _ => {
52                    return Err(SgfErrorKind::ParseError.into());
53                }
54            }
55        }
56        let mut iter = nodes.iter();
57        if is_root {
58            iter.next();
59        }
60        let in_valid = iter.any(|node| node.tokens.iter().any(|token| token.is_root_token()));
61        if in_valid {
62            Err(SgfErrorKind::InvalidRootTokenPlacement.into())
63        } else {
64            Ok(GameTree { nodes, variations })
65        }
66    } else {
67        Err(SgfErrorKind::ParseError.into())
68    }
69}
70
71/// Parses a sequence of nodes to be added to a `GameTree`
72fn parse_sequence(sequence_nodes: Vec<ParserNode<'_>>) -> Result<Vec<GameNode>, SgfError> {
73    let mut nodes = vec![];
74    for sequence_node in &sequence_nodes {
75        if let ParserNode::Node(node_tokens) = sequence_node {
76            let mut tokens: Vec<SgfToken> = vec![];
77            for t in node_tokens {
78                if let ParserNode::Token(new_tokens) = t {
79                    tokens.extend(new_tokens.clone());
80                } else {
81                    return Err(SgfErrorKind::ParseError.into());
82                }
83            }
84            nodes.push(GameNode { tokens });
85        } else {
86            return Err(SgfErrorKind::ParseError.into());
87        }
88    }
89    Ok(nodes)
90}
91
92/// Intermediate nodes from parsing the SGF file
93#[derive(Debug, PartialEq, Clone)]
94enum ParserNode<'a> {
95    Token(Vec<SgfToken>),
96    Text(&'a str),
97    Node(Vec<ParserNode<'a>>),
98    Sequence(Vec<ParserNode<'a>>),
99    GameTree(Vec<ParserNode<'a>>),
100}
101
102fn parse_pair(pair: Pair<'_, Rule>) -> ParserNode<'_> {
103    match pair.as_rule() {
104        Rule::game_tree => ParserNode::GameTree(pair.into_inner().map(parse_pair).collect()),
105        Rule::sequence => ParserNode::Sequence(pair.into_inner().map(parse_pair).collect()),
106        Rule::node => ParserNode::Node(pair.into_inner().map(parse_pair).collect()),
107        Rule::property => {
108            let text_nodes = pair.into_inner().map(parse_pair).collect::<Vec<_>>();
109            let (_, ts) = text_nodes
110                .iter()
111                .try_fold((None, vec![]), |(ident, mut tokens), value| {
112                    if let ParserNode::Text(value) = value {
113                        match ident {
114                            None => Some((Some(*value), tokens)),
115                            Some(id) => {
116                                tokens.push(SgfToken::from_pair(id, value));
117                                Some((ident, tokens))
118                            }
119                        }
120                    } else {
121                        None
122                    }
123                })
124                .expect(
125                    "Pest parsing guarantee that all properties have an identifier and a value",
126                );
127            ParserNode::Token(ts)
128        }
129        Rule::property_identifier => ParserNode::Text(pair.as_str()),
130        Rule::property_value => {
131            let value = pair.as_str();
132            let end = value.len() - 1;
133            ParserNode::Text(&value[1..end])
134        }
135        Rule::inner => {
136            unreachable!();
137        }
138        Rule::char => {
139            unreachable!();
140        }
141        Rule::WHITESPACE => {
142            unreachable!();
143        }
144    }
145}