sgf_tool/
parser.rs

1use std::borrow::Cow;
2
3use crate::parser::inner_parser::{Rule, SgfParser};
4use crate::{Base, Figure, Move, Player, Point, PointRange, PointText, SgfToolError, Token};
5
6use pest::iterators::{Pair, Pairs};
7use pest::Parser;
8
9pub(crate) mod inner_parser {
10    use pest_derive::Parser;
11    #[derive(Parser)]
12    #[grammar = "sgf.pest"]
13    pub(crate) struct SgfParser;
14}
15
16fn parse_string(mut rules: Pairs<'_, Rule>) -> Result<&'_ str, SgfToolError> {
17    if let Some(inner_rule) = rules.next() {
18        return Ok(inner_rule.as_str());
19    }
20
21    Err(SgfToolError::InvalidString)
22}
23
24fn parse_figure(mut rules: Pairs<'_, Rule>) -> Result<Option<Figure<'_>>, SgfToolError> {
25    let node = match rules.next() {
26        Some(node) => node.as_str(),
27        None => return Ok(None),
28    };
29
30    if let Some(index) = node.find(':') {
31        let text = &node[index + 1..];
32        let number = node[0..index]
33            .parse::<usize>()
34            .map_err(|_| SgfToolError::InvalidNumber)?;
35        return Ok(Some(Figure(number, text)));
36    }
37
38    Ok(None)
39}
40
41fn parse_usize(mut rules: Pairs<'_, Rule>) -> Result<usize, SgfToolError> {
42    if let Some(inner_rule) = rules.next() {
43        return inner_rule
44            .as_str()
45            .parse::<usize>()
46            .map_err(|_| SgfToolError::InvalidNumber);
47    }
48
49    Err(SgfToolError::InvalidNumber)
50}
51
52fn parse_float(mut rules: Pairs<'_, Rule>) -> Result<f32, SgfToolError> {
53    if let Some(inner_rule) = rules.next() {
54        return inner_rule
55            .as_str()
56            .parse::<f32>()
57            .map_err(|_| SgfToolError::InvalidFloat);
58    }
59
60    Err(SgfToolError::InvalidFloat)
61}
62
63fn parse_player(mut rules: Pairs<'_, Rule>) -> Result<Player, SgfToolError> {
64    if let Some(inner_rule) = rules.next() {
65        return Ok(match &inner_rule.as_str().to_uppercase()[..] {
66            "B" => Player::Black,
67            "W" => Player::White,
68            _ => return Err(SgfToolError::PlayerInformationNotValid),
69        });
70    }
71
72    Err(SgfToolError::InvalidNumber)
73}
74
75fn parse_stones(rules: Pairs<'_, Rule>) -> Result<Vec<Point<'_>>, SgfToolError> {
76    let mut stones = Vec::new();
77
78    for rule in rules {
79        stones.push(Point(rule.as_str()));
80    }
81
82    Ok(stones)
83}
84
85fn parse_stone_texts(rules: Pairs<'_, Rule>) -> Result<Vec<PointText<'_>>, SgfToolError> {
86    let mut stone_texts = Vec::new();
87
88    for rule in rules {
89        if let Some(index) = rule.as_str().find(':') {
90            let stone = Point(&rule.as_str()[index + 1..]);
91            let text = &rule.as_str()[0..index];
92            stone_texts.push(PointText(stone, text));
93        }
94    }
95
96    Ok(stone_texts)
97}
98
99fn parse_move(mut rules: Pairs<'_, Rule>) -> Result<Move<'_>, SgfToolError> {
100    if let Some(inner_rule) = rules.next() {
101        return Ok(Move::Move(Point(inner_rule.as_str())));
102    }
103    Ok(Move::Pass)
104}
105
106fn parse_point_range(mut rules: Pairs<'_, Rule>) -> Result<PointRange<'_>, SgfToolError> {
107    if let Some(inner_rule) = rules.next() {
108        let stones = inner_rule.as_str().split(':').collect::<Vec<_>>();
109        return Ok(PointRange(Point(stones[0]), Point(stones[1])));
110    }
111
112    Err(SgfToolError::PointInformationNotValid)
113}
114
115fn parse_point_ranges(rules: Pairs<'_, Rule>) -> Result<Vec<PointRange<'_>>, SgfToolError> {
116    let mut ranges = Vec::new();
117    for rule in rules {
118        let stones = rule.as_str().split(':').collect::<Vec<_>>();
119        ranges.push(PointRange(Point(stones[0]), Point(stones[1])));
120    }
121    Ok(ranges)
122}
123
124fn parse_node(pair: Pair<'_, Rule>) -> Result<Token<'_>, SgfToolError> {
125    let mut inner_rules = pair.into_inner();
126
127    if let Some(rule) = inner_rules.next() {
128        if let Rule::node_type = rule.as_rule() {
129            rule.as_str();
130            let result = match &rule.as_str().to_uppercase()[..] {
131                "AP" => Token::Application(parse_string(inner_rules)?),
132                "C" => Token::Comment(parse_string(inner_rules)?),
133                "CP" => Token::Copyright(parse_string(inner_rules)?),
134                "PB" => Token::BlackName(parse_string(inner_rules)?),
135                "PW" => Token::WhiteName(parse_string(inner_rules)?),
136                "BT" => Token::BlackTeam(parse_string(inner_rules)?),
137                "WT" => Token::WhiteTeam(parse_string(inner_rules)?),
138                "FF" => Token::FileFormat(parse_usize(inner_rules)?),
139                "GM" => Token::GameType(parse_usize(inner_rules)?),
140                "CA" => Token::Charset(parse_string(inner_rules)?),
141                "ST" => Token::VariationShown(parse_usize(inner_rules)?),
142                "PL" => Token::WhoseTurn(parse_player(inner_rules)?),
143                "AB" => Token::BlackStones(parse_stones(inner_rules)?),
144                "AW" => Token::WhiteStones(parse_stones(inner_rules)?),
145                "SO" => Token::Source(parse_string(inner_rules)?),
146                "GN" => Token::GameName(parse_string(inner_rules)?),
147                "N" => Token::NodeName(parse_string(inner_rules)?),
148                "B" => Token::BlackMove(parse_move(inner_rules)?),
149                "W" => Token::WhiteMove(parse_move(inner_rules)?),
150                "RU" => Token::Rule(parse_string(inner_rules)?),
151                "KM" => Token::Komi(parse_float(inner_rules)?),
152                "AR" => Token::DrawArrow(parse_point_range(inner_rules)?),
153                "CR" => Token::DrawCircle(parse_stones(inner_rules)?),
154                "DD" => Token::GreyOut(parse_stones(inner_rules)?),
155                "MA" => Token::MarkX(parse_stones(inner_rules)?),
156                "SQ" => Token::DrawSquare(parse_stones(inner_rules)?),
157                "TR" => Token::DrawTriangle(parse_stones(inner_rules)?),
158                "AN" => Token::PersonWhoProvidesAnnotations(parse_string(inner_rules)?),
159                "BR" => Token::BlackPlayerRank(parse_string(inner_rules)?),
160                "WR" => Token::WhitePlayerRank(parse_string(inner_rules)?),
161                "HA" => Token::Handicap(parse_usize(inner_rules)?),
162                "RE" => Token::Result(parse_string(inner_rules)?),
163                "FG" => Token::Figure(parse_figure(inner_rules)?),
164                "PM" => Token::Printing(parse_usize(inner_rules)?),
165                "TM" => Token::TimeLimit(parse_usize(inner_rules)?),
166                "DT" => Token::Date(parse_string(inner_rules)?),
167                "EV" => Token::Event(parse_string(inner_rules)?),
168                "LB" => Token::PointText(parse_stone_texts(inner_rules)?),
169                "RO" => Token::Round(parse_string(inner_rules)?),
170                "US" => Token::SGFCreator(parse_string(inner_rules)?),
171                "VW" => Token::ViewOnly(parse_point_ranges(inner_rules)?),
172                "MN" => Token::MoveNumber(parse_usize(inner_rules)?),
173                "SZ" => {
174                    let size = parse_usize(inner_rules)?;
175                    Token::BoardSize(size, size)
176                }
177                _ => {
178                    debug_assert!(false, "Unknown rule: {:?}", rule);
179                    Token::Unknown(rule.as_str())
180                }
181            };
182            return Ok(result);
183        }
184    }
185
186    Err(SgfToolError::NodeInformationNotValid)
187}
188
189fn parse_rule(pair: Pair<'_, Rule>) -> Result<Token<'_>, SgfToolError> {
190    match pair.as_rule() {
191        Rule::node => parse_node(pair),
192        Rule::object => {
193            let mut base = Base::default();
194
195            for pair in pair.into_inner() {
196                base.tokens.push(Cow::Owned(parse_rule(pair)?));
197            }
198            Ok(Token::Variation(base))
199        }
200        _ => Err(SgfToolError::ParseFailed),
201    }
202}
203
204fn parse_pair(pair: Pair<'_, Rule>) -> Result<Base<'_>, SgfToolError> {
205    let mut base = Base { tokens: Vec::new() };
206
207    for inner_pair in pair.into_inner() {
208        base.tokens.push(Cow::Owned(parse_rule(inner_pair)?));
209    }
210
211    Ok(base)
212}
213
214pub fn parse(text: &str) -> Result<Base<'_>, SgfToolError> {
215    let pairs = SgfParser::parse(Rule::file, text).map_err(|_| SgfToolError::SyntaxIssue)?;
216
217    if let Some(object) = pairs.into_iter().next() {
218        return parse_pair(object);
219    }
220
221    Err(SgfToolError::RootObjectNotFound)
222}