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}