Skip to content

Commit 6e6f26b

Browse files
authored
refactor: improve error handling and pgn parsing (#167)
1 parent 20bfb24 commit 6e6f26b

File tree

6 files changed

+210
-50
lines changed

6 files changed

+210
-50
lines changed

README.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ The 3 other big chess libraries that I know of in C++ are:
8181
- Texel tuner for HCE engines
8282
<https://github.com/GediminasMasaitis/texel-tuner>
8383

84+
### Exceptions
85+
86+
This library might throw exceptions in some cases, for example when the input is invalid or things are not as expected.
87+
To disable exceptions, define `CHESS_NO_EXCEPTIONS` before including the header.
88+
8489
### Benchmarks
8590

8691
Tested on Ryzen 9 5950X:
@@ -109,10 +114,13 @@ depth 6 time 2165 nodes 872323796 nps 402734901 fen 4rrb1/1kp3b1/1p1p4/pP1P
109114
depth 6 time 6382 nodes 2678022813 nps 419555508 fen 1rkr3b/1ppn3p/3pB1n1/6q1/R2P4/4N1P1/1P5P/2KRQ1B1 b Ddb - 0 14
110115
```
111116

112-
#### Exceptions
117+
### PGN Parser
113118

114-
This library might throw exceptions in some cases, for example when the input is invalid or things are not as expected.
115-
To disable exceptions, define `CHESS_NO_EXCEPTIONS` before including the header.
119+
Ran with `lichess_db_standard_rated_2017-03.pgn` on a Ryzen 9 5950X with Samsung 980 SSD.
120+
121+
| Benchmark | Time | Throughput |
122+
| :---: | :---: | :---: |
123+
| ./build/example | 28.927s | 413.281 MB/s |
116124

117125
#### PERFT Comparison
118126

@@ -121,6 +129,7 @@ To disable exceptions, define `CHESS_NO_EXCEPTIONS` before including the header.
121129
`chess-library` had no movelist preallocation, same as the other libraries.
122130

123131
```
132+
124133
chess-library:
125134
depth 6 time 539 nodes 119060324 nps 220482081 fen rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
126135
depth 5 time 538 nodes 193690690 nps 359351929 fen r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1
@@ -144,6 +153,7 @@ depth 6 time 404 nodes 11030083 nps 27234772 fen 8/2p5/3p4/KP5r/1R3p1k/8
144153
depth 5 time 388 nodes 15833292 nps 40702550 fen r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1
145154
depth 5 time 2909 nodes 89941194 nps 30907626 fen rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8
146155
depth 5 time 3403 nodes 164075551 nps 48200808 fen r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 11
156+
147157
```
148158

149159
### Development Setup

docs/pages/pgn-parsing.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ Create the parser and parse the games:
6868

6969
```cpp
7070
pgn::StreamParser parser(file_stream);
71-
parser.readGames(visitor);
71+
auto error = parser.readGames(visitor);
7272
```
7373
7474
#### Putting it together
@@ -112,7 +112,12 @@ int main() {
112112
113113
MyVisitor visitor;
114114
pgn::StreamParser parser(file_stream);
115-
parser.readGames(visitor);
115+
auto error = parser.readGames(visitor);
116+
117+
if (error) {
118+
std::cerr << "Error parsing PGN: " << error.message() << std::endl;
119+
return -1;
120+
}
116121
117122
return 0;
118123
}

example/main.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,12 @@ int main(int argc, char const* argv[]) {
3838
const auto t0 = std::chrono::high_resolution_clock::now();
3939

4040
pgn::StreamParser parser(file_stream);
41-
parser.readGames(*vis);
41+
auto error = parser.readGames(*vis);
42+
43+
if (error) {
44+
std::cerr << "Error: " << error.message() << "\n";
45+
return 1;
46+
}
4247

4348
const auto t1 = std::chrono::high_resolution_clock::now();
4449

include/chess.hpp

Lines changed: 91 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ THIS FILE IS AUTO GENERATED DO NOT CHANGE MANUALLY.
2525
2626
Source: https://github.com/Disservin/chess-library
2727
28-
VERSION: 0.8.3
28+
VERSION: 0.8.4
2929
*/
3030

3131
#ifndef CHESS_HPP
@@ -3935,11 +3935,49 @@ class Visitor {
39353935
bool skip_ = false;
39363936
};
39373937

3938-
enum class StreamParserError {
3939-
None,
3940-
InvalidHeaderMissingClosingBracket,
3941-
InvalidHeaderMissingClosingQuote,
3942-
NotEnoughData,
3938+
class StreamParserError {
3939+
public:
3940+
enum Code {
3941+
None,
3942+
ExceededMaxStringLength,
3943+
InvalidHeaderMissingClosingBracket,
3944+
InvalidHeaderMissingClosingQuote,
3945+
NotEnoughData
3946+
};
3947+
3948+
StreamParserError() : code_(None) {}
3949+
3950+
StreamParserError(Code code) : code_(code) {}
3951+
3952+
Code code() const { return code_; }
3953+
3954+
bool hasError() const { return code_ != None; }
3955+
3956+
std::string message() const {
3957+
switch (code_) {
3958+
case None:
3959+
return "No error";
3960+
case InvalidHeaderMissingClosingBracket:
3961+
return "Invalid header: missing closing bracket";
3962+
case InvalidHeaderMissingClosingQuote:
3963+
return "Invalid header: missing closing quote";
3964+
case NotEnoughData:
3965+
return "Not enough data";
3966+
default:
3967+
assert(false);
3968+
return "Unknown error";
3969+
}
3970+
}
3971+
3972+
bool operator==(Code code) const { return code_ == code; }
3973+
bool operator!=(Code code) const { return code_ != code; }
3974+
bool operator==(const StreamParserError &other) const { return code_ == other.code_; }
3975+
bool operator!=(const StreamParserError &other) const { return code_ != other.code_; }
3976+
3977+
operator bool() const { return code_ != None; }
3978+
3979+
private:
3980+
Code code_;
39433981
};
39443982

39453983
template <std::size_t BUFFER_SIZE =
@@ -3981,6 +4019,10 @@ class StreamParser {
39814019

39824020
} else if (in_body) {
39834021
processBody();
4022+
4023+
if (error != StreamParserError::None) {
4024+
return error;
4025+
}
39844026
}
39854027

39864028
if (!dont_advance_after_body) stream_buffer.advance();
@@ -3997,17 +4039,31 @@ class StreamParser {
39974039
private:
39984040
class LineBuffer {
39994041
public:
4000-
LineBuffer() { buffer_.reserve(BUFFER_SIZE); }
4001-
bool empty() const noexcept { return buffer_.empty(); }
4042+
bool empty() const noexcept { return index_ == 0; }
40024043

4003-
void clear() noexcept { buffer_.clear(); }
4044+
void clear() noexcept { index_ = 0; }
40044045

4005-
std::string_view get() const noexcept { return std::string_view(buffer_.data(), buffer_.size()); }
4046+
std::string_view get() const noexcept { return std::string_view(buffer_.data(), index_); }
40064047

4007-
void operator+=(char c) { buffer_.push_back(c); }
4048+
bool add(char c) {
4049+
if (index_ >= N) {
4050+
return false;
4051+
}
4052+
4053+
buffer_[index_] = c;
4054+
4055+
++index_;
4056+
4057+
return true;
4058+
}
40084059

40094060
private:
4010-
std::vector<char> buffer_;
4061+
// PGN lines are limited to 255 characters
4062+
static constexpr int N = 255;
4063+
4064+
std::array<char, N> buffer_ = {};
4065+
4066+
std::size_t index_ = 0;
40114067
};
40124068

40134069
class StreamBuffer {
@@ -4125,7 +4181,7 @@ class StreamParser {
41254181

41264182
void callVisitorMoveFunction() {
41274183
if (!move.empty()) {
4128-
if (!visitor->skip()) visitor->move(move.get(), comment.get());
4184+
if (!visitor->skip()) visitor->move(move.get(), comment);
41294185

41304186
move.clear();
41314187
comment.clear();
@@ -4145,7 +4201,11 @@ class StreamParser {
41454201
if (is_space(*k)) {
41464202
break;
41474203
} else {
4148-
header.first += *k;
4204+
if (!header.first.add(*k)) {
4205+
error = StreamParserError::ExceededMaxStringLength;
4206+
return;
4207+
}
4208+
41494209
stream_buffer.advance();
41504210
}
41514211
}
@@ -4179,7 +4239,12 @@ class StreamParser {
41794239
return;
41804240
} else {
41814241
backslash = false;
4182-
header.second += *k;
4242+
4243+
if (!header.second.add(*k)) {
4244+
error = StreamParserError::ExceededMaxStringLength;
4245+
return;
4246+
}
4247+
41834248
stream_buffer.advance();
41844249
}
41854250
}
@@ -4253,7 +4318,7 @@ class StreamParser {
42534318

42544319
// the game has no moves, but a comment followed by a game termination
42554320
if (!visitor->skip()) {
4256-
visitor->move("", comment.get());
4321+
visitor->move("", comment);
42574322

42584323
comment.clear();
42594324
}
@@ -4393,8 +4458,10 @@ class StreamParser {
43934458
}
43944459
// castling
43954460
else {
4396-
move += '0';
4397-
move += '-';
4461+
if (!move.add('0') || !move.add('-')) {
4462+
error = StreamParserError::ExceededMaxStringLength;
4463+
return;
4464+
}
43984465

43994466
if (parseMove()) {
44004467
stream_buffer.advance();
@@ -4412,7 +4479,10 @@ class StreamParser {
44124479
break;
44134480
}
44144481

4415-
move += *c;
4482+
if (!move.add(*c)) {
4483+
error = StreamParserError::ExceededMaxStringLength;
4484+
return true;
4485+
}
44164486

44174487
stream_buffer.advance();
44184488
}
@@ -4526,8 +4596,8 @@ class StreamParser {
45264596
// one time allocations
45274597
std::pair<LineBuffer, LineBuffer> header = {LineBuffer{}, LineBuffer{}};
45284598

4529-
LineBuffer move = {};
4530-
LineBuffer comment = {};
4599+
LineBuffer move = {};
4600+
std::string comment = {};
45314601

45324602
// State
45334603

src/include.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ THIS FILE IS AUTO GENERATED DO NOT CHANGE MANUALLY.
2525
2626
Source: https://github.com/Disservin/chess-library
2727
28-
VERSION: 0.8.3
28+
VERSION: 0.8.4
2929
*/
3030

3131
#ifndef CHESS_HPP

0 commit comments

Comments
 (0)