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)22bool 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)33void Reader::consume_char([[maybe_unused]] char expected) 34 { 35 [[maybe_unused]] char c = read_char(); 36 LIBBOARDGAME_ASSERT(c == expected); 37 } 38 consume_whitespace()39void Reader::consume_whitespace() 40 { 41 while (is_ascii_space(peek())) 42 m_in->get(); 43 } 44 on_begin_node(bool is_root)45void Reader::on_begin_node([[maybe_unused]] bool is_root) 46 { 47 // Default implementation does nothing 48 } 49 on_begin_tree(bool is_root)50void Reader::on_begin_tree([[maybe_unused]] bool is_root) 51 { 52 // Default implementation does nothing 53 } 54 on_end_node()55void Reader::on_end_node() 56 { 57 // Default implementation does nothing 58 } 59 on_end_tree(bool is_root)60void 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)65void Reader::on_property([[maybe_unused]] const string& id, 66 [[maybe_unused]] const vector<string>& values) 67 { 68 // Default implementation does nothing 69 } 70 peek()71char 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)79bool Reader::read(istream& in, bool check_single_tree) 80 { 81 m_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)103void 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()118char 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)133void Reader::read_expected(char expected) 134 { 135 if (read_char() != expected) 136 throw ReadError(string("Expected '") + expected + "'"); 137 } 138 read_node(bool is_root)139void 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()156void 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)214void 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