1 //-----------------------------------------------------------------------------
2 /** @file libboardgame_base/Reader.cpp
3     @author Markus Enzenberger
4     @copyright GNU General Public License version 3 or later */
5 //-----------------------------------------------------------------------------
6 
7 #include "Reader.h"
8 
9 #include <cctype>
10 #include <cstdio>
11 #include <fstream>
12 #include "Assert.h"
13 
14 namespace libboardgame_base {
15 
16 //-----------------------------------------------------------------------------
17 
18 namespace {
19 
20 /** Replacement for std::isspace() that returns true only for whitespaces
21     in the ASCII range. */
is_ascii_space(int c)22 bool is_ascii_space(int c)
23 {
24     return c >= 0 && c < 128 && isspace(c) != 0;
25 }
26 
27 } // namespace
28 
29 //-----------------------------------------------------------------------------
30 
31 Reader::~Reader() = default; // Non-inline to avoid GCC -Winline warning
32 
consume_char(char expected)33 void Reader::consume_char([[maybe_unused]] char expected)
34 {
35     [[maybe_unused]] char c = read_char();
36     LIBBOARDGAME_ASSERT(c == expected);
37 }
38 
consume_whitespace()39 void Reader::consume_whitespace()
40 {
41     while (is_ascii_space(peek()))
42         m_in->get();
43 }
44 
on_begin_node(bool is_root)45 void Reader::on_begin_node([[maybe_unused]] bool is_root)
46 {
47     // Default implementation does nothing
48 }
49 
on_begin_tree(bool is_root)50 void Reader::on_begin_tree([[maybe_unused]] bool is_root)
51 {
52     // Default implementation does nothing
53 }
54 
on_end_node()55 void Reader::on_end_node()
56 {
57     // Default implementation does nothing
58 }
59 
on_end_tree(bool is_root)60 void Reader::on_end_tree([[maybe_unused]] bool is_root)
61 {
62     // Default implementation does nothing
63 }
64 
on_property(const string & id,const vector<string> & values)65 void Reader::on_property([[maybe_unused]] const string& id,
66                          [[maybe_unused]] const vector<string>& values)
67 {
68     // Default implementation does nothing
69 }
70 
peek()71 char Reader::peek()
72 {
73     int c = m_in->peek();
74     if (c == EOF)
75         throw ReadError("Unexpected end of input");
76     return char(c);
77 }
78 
read(istream & in,bool check_single_tree)79 bool Reader::read(istream& in, bool check_single_tree)
80 {
81     m_in = &in;
82     m_is_in_main_variation = true;
83     consume_whitespace();
84     read_tree(true);
85     while (true)
86     {
87         int c = m_in->peek();
88         if (c == EOF)
89             return false;
90         if (c == '(')
91         {
92             if (check_single_tree)
93                 throw ReadError("Input has multiple game trees");
94             return true;
95         }
96         if (is_ascii_space(c))
97             m_in->get();
98         else
99             throw ReadError("Extra characters after end of tree.");
100     }
101 }
102 
read(const string & file)103 void Reader::read(const string& file)
104 {
105     ifstream in(file);
106     if (! in)
107         throw ReadError("Could not open '" + file + "'");
108     try
109     {
110         read(in);
111     }
112     catch (const ReadError& e)
113     {
114         throw ReadError("Could not read '" + file + "': " + e.what());
115     }
116 }
117 
read_char()118 char Reader::read_char()
119 {
120     int c = m_in->get();
121     if (c == EOF)
122         throw ReadError("Unexpected end of SGF stream");
123     if (c == '\r')
124     {
125         // Convert CR+LF or single CR into LF
126         if (peek() == '\n')
127             m_in->get();
128         return '\n';
129     }
130     return char(c);
131 }
132 
read_expected(char expected)133 void Reader::read_expected(char expected)
134 {
135     if (read_char() != expected)
136         throw ReadError(string("Expected '") + expected + "'");
137 }
138 
read_node(bool is_root)139 void Reader::read_node(bool is_root)
140 {
141     read_expected(';');
142     if (! m_read_only_main_variation || m_is_in_main_variation)
143         on_begin_node(is_root);
144     while (true)
145     {
146         consume_whitespace();
147         char c = peek();
148         if (c == '(' || c == ')' || c == ';')
149             break;
150         read_property();
151     }
152     if (! m_read_only_main_variation || m_is_in_main_variation)
153         on_end_node();
154 }
155 
read_property()156 void Reader::read_property()
157 {
158     if (m_read_only_main_variation && ! m_is_in_main_variation)
159     {
160         while (peek() != '[')
161             read_char();
162         while (peek() == '[')
163         {
164             consume_char('[');
165             bool escape = false;
166             while (peek() != ']' || escape)
167             {
168                 char c = read_char();
169                 if (c == '\\' && ! escape)
170                 {
171                     escape = true;
172                     continue;
173                 }
174                 escape = false;
175             }
176             consume_char(']');
177             consume_whitespace();
178         }
179     }
180     else
181     {
182         m_id.clear();
183         while (peek() != '[')
184         {
185             char c = read_char();
186             if (! is_ascii_space(c))
187                 m_id += c;
188         }
189         m_values.clear();
190         while (peek() == '[')
191         {
192             consume_char('[');
193             m_value.clear();
194             bool escape = false;
195             while (peek() != ']' || escape)
196             {
197                 char c = read_char();
198                 if (c == '\\' && ! escape)
199                 {
200                     escape = true;
201                     continue;
202                 }
203                 escape = false;
204                 m_value += c;
205             }
206             consume_char(']');
207             consume_whitespace();
208             m_values.push_back(m_value);
209         }
210         on_property(m_id, m_values);
211     }
212 }
213 
read_tree(bool is_root)214 void Reader::read_tree(bool is_root)
215 {
216     read_expected('(');
217     on_begin_tree(is_root);
218     bool was_root = is_root;
219     while (true)
220     {
221         consume_whitespace();
222         char c = peek();
223         if (c == ')')
224             break;
225         if (c == ';')
226         {
227             read_node(is_root);
228             is_root = false;
229         }
230         else if (c == '(')
231             read_tree(false);
232         else
233             throw ReadError("Extra text before node");
234     }
235     read_expected(')');
236     m_is_in_main_variation = false;
237     on_end_tree(was_root);
238 }
239 
240 //-----------------------------------------------------------------------------
241 
242 } // namespace libboardgame_base
243