rust-pgn-reader-python-binding 3.1.0__tar.gz → 3.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (19) hide show
  1. {rust_pgn_reader_python_binding-3.1.0 → rust_pgn_reader_python_binding-3.2.0}/Cargo.lock +43 -51
  2. {rust_pgn_reader_python_binding-3.1.0 → rust_pgn_reader_python_binding-3.2.0}/Cargo.toml +6 -5
  3. rust_pgn_reader_python_binding-3.2.0/PKG-INFO +7 -0
  4. {rust_pgn_reader_python_binding-3.1.0 → rust_pgn_reader_python_binding-3.2.0}/benches/parquet_bench.rs +0 -7
  5. rust_pgn_reader_python_binding-3.2.0/rust_pgn_reader_python_binding.pyi +49 -0
  6. {rust_pgn_reader_python_binding-3.1.0 → rust_pgn_reader_python_binding-3.2.0}/src/comment_parsing.rs +96 -76
  7. {rust_pgn_reader_python_binding-3.1.0 → rust_pgn_reader_python_binding-3.2.0}/src/example_str.py +1 -0
  8. {rust_pgn_reader_python_binding-3.1.0 → rust_pgn_reader_python_binding-3.2.0}/src/lib.rs +62 -27
  9. rust_pgn_reader_python_binding-3.1.0/PKG-INFO +0 -54
  10. {rust_pgn_reader_python_binding-3.1.0 → rust_pgn_reader_python_binding-3.2.0}/.github/workflows/CI.yml +0 -0
  11. {rust_pgn_reader_python_binding-3.1.0 → rust_pgn_reader_python_binding-3.2.0}/.gitignore +0 -0
  12. {rust_pgn_reader_python_binding-3.1.0 → rust_pgn_reader_python_binding-3.2.0}/README.md +0 -0
  13. {rust_pgn_reader_python_binding-3.1.0 → rust_pgn_reader_python_binding-3.2.0}/pyproject.toml +0 -0
  14. {rust_pgn_reader_python_binding-3.1.0 → rust_pgn_reader_python_binding-3.2.0}/src/bench.py +0 -0
  15. {rust_pgn_reader_python_binding-3.1.0 → rust_pgn_reader_python_binding-3.2.0}/src/bench_parquet.py +0 -0
  16. {rust_pgn_reader_python_binding-3.1.0 → rust_pgn_reader_python_binding-3.2.0}/src/bench_parquet_arrow.py +0 -0
  17. {rust_pgn_reader_python_binding-3.1.0 → rust_pgn_reader_python_binding-3.2.0}/src/bench_parquet_parallel.py +0 -0
  18. {rust_pgn_reader_python_binding-3.1.0 → rust_pgn_reader_python_binding-3.2.0}/src/example_rowgroup.py +0 -0
  19. {rust_pgn_reader_python_binding-3.1.0 → rust_pgn_reader_python_binding-3.2.0}/src/test.py +0 -0
@@ -341,9 +341,9 @@ dependencies = [
341
341
 
342
342
  [[package]]
343
343
  name = "btoi"
344
- version = "0.4.3"
344
+ version = "0.5.0"
345
345
  source = "registry+https://github.com/rust-lang/crates.io-index"
346
- checksum = "9dd6407f73a9b8b6162d8a2ef999fe6afd7cc15902ebf42c5cd296addf17e0ad"
346
+ checksum = "3b5ab9db53bcda568284df0fd39f6eac24ad6f7ba7ff1168b9e76eba6576b976"
347
347
  dependencies = [
348
348
  "num-traits",
349
349
  ]
@@ -449,12 +449,6 @@ dependencies = [
449
449
  "half",
450
450
  ]
451
451
 
452
- [[package]]
453
- name = "circular"
454
- version = "0.3.0"
455
- source = "registry+https://github.com/rust-lang/crates.io-index"
456
- checksum = "b0fc239e0f6cb375d2402d48afb92f76f5404fd1df208a41930ec81eda078bea"
457
-
458
452
  [[package]]
459
453
  name = "clap"
460
454
  version = "4.5.39"
@@ -602,9 +596,9 @@ dependencies = [
602
596
 
603
597
  [[package]]
604
598
  name = "either"
605
- version = "1.15.0"
599
+ version = "1.13.0"
606
600
  source = "registry+https://github.com/rust-lang/crates.io-index"
607
- checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
601
+ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
608
602
 
609
603
  [[package]]
610
604
  name = "equivalent"
@@ -721,9 +715,9 @@ dependencies = [
721
715
 
722
716
  [[package]]
723
717
  name = "indoc"
724
- version = "2.0.6"
718
+ version = "2.0.5"
725
719
  source = "registry+https://github.com/rust-lang/crates.io-index"
726
- checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
720
+ checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
727
721
 
728
722
  [[package]]
729
723
  name = "integer-encoding"
@@ -757,11 +751,10 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
757
751
 
758
752
  [[package]]
759
753
  name = "jobserver"
760
- version = "0.1.33"
754
+ version = "0.1.32"
761
755
  source = "registry+https://github.com/rust-lang/crates.io-index"
762
- checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
756
+ checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
763
757
  dependencies = [
764
- "getrandom 0.3.3",
765
758
  "libc",
766
759
  ]
767
760
 
@@ -847,9 +840,9 @@ dependencies = [
847
840
 
848
841
  [[package]]
849
842
  name = "libc"
850
- version = "0.2.172"
843
+ version = "0.2.169"
851
844
  source = "registry+https://github.com/rust-lang/crates.io-index"
852
- checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
845
+ checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
853
846
 
854
847
  [[package]]
855
848
  name = "libm"
@@ -1048,9 +1041,9 @@ dependencies = [
1048
1041
 
1049
1042
  [[package]]
1050
1043
  name = "once_cell"
1051
- version = "1.21.3"
1044
+ version = "1.20.2"
1052
1045
  source = "registry+https://github.com/rust-lang/crates.io-index"
1053
- checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
1046
+ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
1054
1047
 
1055
1048
  [[package]]
1056
1049
  name = "oorandom"
@@ -1117,12 +1110,11 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
1117
1110
 
1118
1111
  [[package]]
1119
1112
  name = "pgn-reader"
1120
- version = "0.26.0"
1113
+ version = "0.28.0"
1121
1114
  source = "registry+https://github.com/rust-lang/crates.io-index"
1122
- checksum = "1994b0ce2a74829bff0462e229b3f0a138cfb293e22c6a65f7966ff26d304e74"
1115
+ checksum = "d0e07db84c19e093741643903738788e6f773237c7eeb60f9a40191afc2a815a"
1123
1116
  dependencies = [
1124
1117
  "btoi",
1125
- "circular",
1126
1118
  "memchr",
1127
1119
  "shakmaty",
1128
1120
  ]
@@ -1201,9 +1193,9 @@ dependencies = [
1201
1193
 
1202
1194
  [[package]]
1203
1195
  name = "portable-atomic"
1204
- version = "1.11.0"
1196
+ version = "1.10.0"
1205
1197
  source = "registry+https://github.com/rust-lang/crates.io-index"
1206
- checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
1198
+ checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6"
1207
1199
 
1208
1200
  [[package]]
1209
1201
  name = "portable-atomic-util"
@@ -1216,18 +1208,18 @@ dependencies = [
1216
1208
 
1217
1209
  [[package]]
1218
1210
  name = "proc-macro2"
1219
- version = "1.0.95"
1211
+ version = "1.0.92"
1220
1212
  source = "registry+https://github.com/rust-lang/crates.io-index"
1221
- checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
1213
+ checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
1222
1214
  dependencies = [
1223
1215
  "unicode-ident",
1224
1216
  ]
1225
1217
 
1226
1218
  [[package]]
1227
1219
  name = "pyo3"
1228
- version = "0.25.0"
1220
+ version = "0.25.1"
1229
1221
  source = "registry+https://github.com/rust-lang/crates.io-index"
1230
- checksum = "f239d656363bcee73afef85277f1b281e8ac6212a1d42aa90e55b90ed43c47a4"
1222
+ checksum = "8970a78afe0628a3e3430376fc5fd76b6b45c4d43360ffd6cdd40bdde72b682a"
1231
1223
  dependencies = [
1232
1224
  "chrono",
1233
1225
  "chrono-tz",
@@ -1264,9 +1256,9 @@ dependencies = [
1264
1256
 
1265
1257
  [[package]]
1266
1258
  name = "pyo3-build-config"
1267
- version = "0.25.0"
1259
+ version = "0.25.1"
1268
1260
  source = "registry+https://github.com/rust-lang/crates.io-index"
1269
- checksum = "755ea671a1c34044fa165247aaf6f419ca39caa6003aee791a0df2713d8f1b6d"
1261
+ checksum = "458eb0c55e7ece017adeba38f2248ff3ac615e53660d7c71a238d7d2a01c7598"
1270
1262
  dependencies = [
1271
1263
  "once_cell",
1272
1264
  "target-lexicon",
@@ -1274,9 +1266,9 @@ dependencies = [
1274
1266
 
1275
1267
  [[package]]
1276
1268
  name = "pyo3-ffi"
1277
- version = "0.25.0"
1269
+ version = "0.25.1"
1278
1270
  source = "registry+https://github.com/rust-lang/crates.io-index"
1279
- checksum = "fc95a2e67091e44791d4ea300ff744be5293f394f1bafd9f78c080814d35956e"
1271
+ checksum = "7114fe5457c61b276ab77c5055f206295b812608083644a5c5b2640c3102565c"
1280
1272
  dependencies = [
1281
1273
  "libc",
1282
1274
  "pyo3-build-config",
@@ -1284,9 +1276,9 @@ dependencies = [
1284
1276
 
1285
1277
  [[package]]
1286
1278
  name = "pyo3-macros"
1287
- version = "0.25.0"
1279
+ version = "0.25.1"
1288
1280
  source = "registry+https://github.com/rust-lang/crates.io-index"
1289
- checksum = "a179641d1b93920829a62f15e87c0ed791b6c8db2271ba0fd7c2686090510214"
1281
+ checksum = "a8725c0a622b374d6cb051d11a0983786448f7785336139c3c94f5aa6bef7e50"
1290
1282
  dependencies = [
1291
1283
  "proc-macro2",
1292
1284
  "pyo3-macros-backend",
@@ -1296,9 +1288,9 @@ dependencies = [
1296
1288
 
1297
1289
  [[package]]
1298
1290
  name = "pyo3-macros-backend"
1299
- version = "0.25.0"
1291
+ version = "0.25.1"
1300
1292
  source = "registry+https://github.com/rust-lang/crates.io-index"
1301
- checksum = "9dff85ebcaab8c441b0e3f7ae40a6963ecea8a9f5e74f647e33fcf5ec9a1e89e"
1293
+ checksum = "4109984c22491085343c05b0dbc54ddc405c3cf7b4374fc533f5c3313a572ccc"
1302
1294
  dependencies = [
1303
1295
  "heck",
1304
1296
  "proc-macro2",
@@ -1309,9 +1301,9 @@ dependencies = [
1309
1301
 
1310
1302
  [[package]]
1311
1303
  name = "quote"
1312
- version = "1.0.40"
1304
+ version = "1.0.37"
1313
1305
  source = "registry+https://github.com/rust-lang/crates.io-index"
1314
- checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
1306
+ checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
1315
1307
  dependencies = [
1316
1308
  "proc-macro2",
1317
1309
  ]
@@ -1394,7 +1386,7 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
1394
1386
 
1395
1387
  [[package]]
1396
1388
  name = "rust_pgn_reader_python_binding"
1397
- version = "3.1.0"
1389
+ version = "3.2.0"
1398
1390
  dependencies = [
1399
1391
  "arrow",
1400
1392
  "arrow-array",
@@ -1491,9 +1483,9 @@ dependencies = [
1491
1483
 
1492
1484
  [[package]]
1493
1485
  name = "shakmaty"
1494
- version = "0.27.3"
1486
+ version = "0.29.1"
1495
1487
  source = "registry+https://github.com/rust-lang/crates.io-index"
1496
- checksum = "f414cadc4e727893d1a3b0dca62aaef4c1c821dcbd969f0b10a92e12d684d53a"
1488
+ checksum = "334bb41280a79854707d79c117c9818ddeaead15108e951c0b082ba2acedd0d1"
1497
1489
  dependencies = [
1498
1490
  "arrayvec",
1499
1491
  "bitflags",
@@ -1534,9 +1526,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
1534
1526
 
1535
1527
  [[package]]
1536
1528
  name = "syn"
1537
- version = "2.0.101"
1529
+ version = "2.0.91"
1538
1530
  source = "registry+https://github.com/rust-lang/crates.io-index"
1539
- checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
1531
+ checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035"
1540
1532
  dependencies = [
1541
1533
  "proc-macro2",
1542
1534
  "quote",
@@ -1617,15 +1609,15 @@ checksum = "e7b17f197b3050ba473acf9181f7b1d3b66d1cf7356c6cc57886662276e65908"
1617
1609
 
1618
1610
  [[package]]
1619
1611
  name = "unicode-ident"
1620
- version = "1.0.18"
1612
+ version = "1.0.14"
1621
1613
  source = "registry+https://github.com/rust-lang/crates.io-index"
1622
- checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
1614
+ checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
1623
1615
 
1624
1616
  [[package]]
1625
1617
  name = "unindent"
1626
- version = "0.2.4"
1618
+ version = "0.2.3"
1627
1619
  source = "registry+https://github.com/rust-lang/crates.io-index"
1628
- checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
1620
+ checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce"
1629
1621
 
1630
1622
  [[package]]
1631
1623
  name = "version_check"
@@ -1913,18 +1905,18 @@ dependencies = [
1913
1905
 
1914
1906
  [[package]]
1915
1907
  name = "zstd-safe"
1916
- version = "7.2.4"
1908
+ version = "7.2.1"
1917
1909
  source = "registry+https://github.com/rust-lang/crates.io-index"
1918
- checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
1910
+ checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059"
1919
1911
  dependencies = [
1920
1912
  "zstd-sys",
1921
1913
  ]
1922
1914
 
1923
1915
  [[package]]
1924
1916
  name = "zstd-sys"
1925
- version = "2.0.15+zstd.1.5.7"
1917
+ version = "2.0.13+zstd.1.5.6"
1926
1918
  source = "registry+https://github.com/rust-lang/crates.io-index"
1927
- checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237"
1919
+ checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa"
1928
1920
  dependencies = [
1929
1921
  "cc",
1930
1922
  "pkg-config",
@@ -1,7 +1,8 @@
1
1
  [package]
2
2
  name = "rust_pgn_reader_python_binding"
3
- version = "3.1.0"
4
- edition = "2021"
3
+ version = "3.2.0"
4
+ edition = "2024"
5
+ readme = "README.md"
5
6
 
6
7
  # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7
8
  [lib]
@@ -10,9 +11,9 @@ crate-type = ["lib", "cdylib"]
10
11
  # lib is needed for the native benchmark
11
12
 
12
13
  [dependencies]
13
- pyo3 = "0.25.0"
14
- shakmaty = "0.27"
15
- pgn-reader = "0.26.0"
14
+ pyo3 = "0.25.1"
15
+ shakmaty = "0.29"
16
+ pgn-reader = "0.28.0"
16
17
  nom = "8.0"
17
18
  rayon = "1.10"
18
19
  num_cpus = "1.17"
@@ -0,0 +1,7 @@
1
+ Metadata-Version: 2.4
2
+ Name: rust_pgn_reader_python_binding
3
+ Version: 3.2.0
4
+ Classifier: Programming Language :: Rust
5
+ Classifier: Programming Language :: Python :: Implementation :: CPython
6
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
7
+ Requires-Python: >=3.8
@@ -1,16 +1,9 @@
1
1
  use arrow::array::{Array, StringArray};
2
-
3
2
  use parquet::arrow::arrow_reader::ParquetRecordBatchReaderBuilder;
4
-
5
- // use parquet::file::reader::{FileReader, SerializedFileReader};
6
- // use parquet::record::RowAccessor;
7
-
8
3
  use std::fs::File;
9
4
  use std::path::Path;
10
5
  use std::time::Instant;
11
6
 
12
- // use criterion::{criterion_group, criterion_main, Criterion};
13
-
14
7
  use rust_pgn_reader_python_binding::parse_multiple_games_native;
15
8
 
16
9
  pub fn bench_parquet() {
@@ -0,0 +1,49 @@
1
+ from typing import List, Optional, Tuple
2
+ import pyarrow
3
+
4
+ class PyUciMove:
5
+ from_square: int
6
+ to_square: int
7
+ promotion: Optional[int]
8
+
9
+ def __init__(
10
+ self, from_square: int, to_square: int, promotion: Optional[int]
11
+ ) -> None: ...
12
+ @property
13
+ def get_from_square_name(self) -> str: ...
14
+ @property
15
+ def get_to_square_name(self) -> str: ...
16
+ @property
17
+ def get_promotion_name(self) -> Optional[str]: ...
18
+ def __str__(self) -> str: ...
19
+ def __repr__(self) -> str: ...
20
+
21
+ class PositionStatus:
22
+ is_checkmate: bool
23
+ is_stalemate: bool
24
+ legal_move_count: int
25
+ is_game_over: bool
26
+ insufficient_material: Tuple[bool, bool]
27
+ turn: bool
28
+
29
+ class MoveExtractor:
30
+ moves: List[PyUciMove]
31
+ valid_moves: bool
32
+ comments: List[str]
33
+ evals: List[float]
34
+ clock_times: List[Tuple[int, int, float]]
35
+ outcome: Optional[str]
36
+ headers: List[Tuple[str, str]]
37
+ castling_rights: List[Tuple[bool, bool, bool, bool]]
38
+ position_status: Optional[PositionStatus]
39
+
40
+ def __init__(self) -> None: ...
41
+ def turn(self) -> bool: ...
42
+
43
+ def parse_game(pgn: str) -> MoveExtractor: ...
44
+ def parse_games(
45
+ pgns: List[str], num_threads: Optional[int] = None
46
+ ) -> List[MoveExtractor]: ...
47
+ def parse_game_moves_arrow_chunked_array(
48
+ pgn_chunked_array: pyarrow.ChunkedArray, num_threads: Optional[int] = None
49
+ ) -> List[MoveExtractor]: ...
@@ -1,12 +1,15 @@
1
1
  use nom::{
2
2
  branch::alt,
3
3
  bytes::complete::{is_not, tag},
4
- character::complete::{char, digit1, multispace1},
4
+ character::complete::{char, digit1},
5
5
  combinator::{map, map_res, not, opt, peek, recognize},
6
6
  multi::{many0, many1},
7
7
  sequence::{delimited, pair, preceded},
8
8
  IResult, Parser,
9
9
  };
10
+ use std::borrow::Cow;
11
+ // use nom::character::complete::multispace1;
12
+ use nom::bytes::complete::take_while1;
10
13
 
11
14
  #[derive(Debug, PartialEq)]
12
15
  pub enum ParsedTag {
@@ -20,35 +23,43 @@ pub enum ParsedTag {
20
23
  }
21
24
 
22
25
  #[derive(Debug, PartialEq)]
23
- pub enum CommentContent {
24
- Text(String),
26
+ pub enum CommentContent<'a> {
27
+ Text(Cow<'a, str>),
25
28
  Tag(ParsedTag),
26
29
  }
27
30
 
28
- pub fn parse_comments(input: &str) -> IResult<&str, Vec<CommentContent>> {
31
+ fn as_str(bytes: &[u8]) -> Result<&str, std::str::Utf8Error> {
32
+ std::str::from_utf8(bytes)
33
+ }
34
+
35
+ fn to_cow_str(bytes: &[u8]) -> CommentContent<'_> {
36
+ CommentContent::Text(String::from_utf8_lossy(bytes))
37
+ }
38
+
39
+ pub fn parse_comments(input: &[u8]) -> IResult<&[u8], Vec<CommentContent<'_>>> {
29
40
  many0(alt((
30
41
  // Attempt to parse a known structured tag first
31
42
  map(parse_structured_tag, CommentContent::Tag),
32
43
  // If not a known tag, but looks like a tag (e.g. [%unknown ...]), parse as text
33
44
  map(
34
45
  recognize(delimited(tag("[%"), is_not("]"), char(']'))),
35
- |s: &str| CommentContent::Text(s.to_string()),
46
+ to_cow_str,
36
47
  ),
37
48
  // Otherwise, parse as regular text content. This must not be empty.
38
49
  map(
39
50
  recognize(many1(alt((
40
- is_not("["), // Takes any char except '['
51
+ is_not([b'[']), // Takes any char except '['
41
52
  // Takes a '[' if it's NOT followed by '%' (to allow "[abc]" as text)
42
53
  recognize(preceded(char('['), peek(not(char('%'))))),
43
54
  )))),
44
- |s: &str| CommentContent::Text(s.to_string()),
55
+ to_cow_str,
45
56
  ),
46
57
  )))
47
58
  .parse(input)
48
59
  }
49
60
 
50
61
  /// Parser for a complete tag like [%eval ...] or [%clk ...]
51
- fn parse_structured_tag(input: &str) -> IResult<&str, ParsedTag> {
62
+ fn parse_structured_tag(input: &[u8]) -> IResult<&[u8], ParsedTag> {
52
63
  delimited(
53
64
  tag("[%"),
54
65
  alt((parse_eval_content, parse_clk_content)),
@@ -58,7 +69,7 @@ fn parse_structured_tag(input: &str) -> IResult<&str, ParsedTag> {
58
69
  }
59
70
 
60
71
  /// Parses the content of an eval tag, e.g., "eval 12.3" or "eval #3"
61
- fn parse_eval_content(input: &str) -> IResult<&str, ParsedTag> {
72
+ fn parse_eval_content(input: &[u8]) -> IResult<&[u8], ParsedTag> {
62
73
  preceded(
63
74
  tag("eval"),
64
75
  preceded(
@@ -73,7 +84,7 @@ fn parse_eval_content(input: &str) -> IResult<&str, ParsedTag> {
73
84
  }
74
85
 
75
86
  /// Parses the content of a clk tag, e.g., "clk 1:23:45.6"
76
- fn parse_clk_content(input: &str) -> IResult<&str, ParsedTag> {
87
+ fn parse_clk_content(input: &[u8]) -> IResult<&[u8], ParsedTag> {
77
88
  preceded(
78
89
  tag("clk"),
79
90
  preceded(
@@ -89,23 +100,29 @@ fn parse_clk_content(input: &str) -> IResult<&str, ParsedTag> {
89
100
  }
90
101
 
91
102
  /// Parser for a signed floating-point number, e.g., "-123.45", "+3.0", "7"
92
- fn parse_signed_float(input: &str) -> IResult<&str, f64> {
103
+ fn parse_signed_float(input: &[u8]) -> IResult<&[u8], f64> {
93
104
  map_res(
94
- recognize(pair(
95
- opt(alt((char('+'), char('-')))),
96
- recognize(pair(digit1, opt(preceded(char('.'), digit1)))),
97
- )),
105
+ map_res(
106
+ recognize(pair(
107
+ opt(alt((char('+'), char('-')))),
108
+ recognize(pair(digit1, opt(preceded(char('.'), digit1)))),
109
+ )),
110
+ as_str,
111
+ ),
98
112
  |s: &str| s.parse::<f64>(),
99
113
  )
100
114
  .parse(input)
101
115
  }
102
116
 
103
117
  /// Parser for a mate value, e.g., "#-3", "#5"
104
- fn parse_mate_value(input: &str) -> IResult<&str, i32> {
118
+ fn parse_mate_value(input: &[u8]) -> IResult<&[u8], i32> {
105
119
  preceded(
106
120
  char('#'),
107
121
  map_res(
108
- recognize(pair(opt(char('-')), digit1)), // Recognizes signed integer
122
+ map_res(
123
+ recognize(pair(opt(char('-')), digit1)), // Recognizes signed integer
124
+ as_str,
125
+ ),
109
126
  |s: &str| s.parse::<i32>(),
110
127
  ),
111
128
  )
@@ -113,12 +130,12 @@ fn parse_mate_value(input: &str) -> IResult<&str, i32> {
113
130
  }
114
131
 
115
132
  /// Parser for a time value in H:M:S format, e.g., "12:34:56" or "1:2:3.45"
116
- fn parse_hms_time(input: &str) -> IResult<&str, (u32, u8, f64)> {
133
+ fn parse_hms_time(input: &[u8]) -> IResult<&[u8], (u32, u8, f64)> {
117
134
  map(
118
135
  (
119
- map_res(digit1, |s: &str| s.parse::<u32>()), // Hours
136
+ map_res(map_res(digit1, as_str), |s: &str| s.parse::<u32>()), // Hours
120
137
  char(':'),
121
- map_res(digit1, |s: &str| s.parse::<u8>()), // Minutes (0-255, typically 0-59)
138
+ map_res(map_res(digit1, as_str), |s: &str| s.parse::<u8>()), // Minutes
122
139
  char(':'),
123
140
  parse_seconds_with_fraction, // Seconds with optional fraction
124
141
  ),
@@ -128,17 +145,20 @@ fn parse_hms_time(input: &str) -> IResult<&str, (u32, u8, f64)> {
128
145
  }
129
146
 
130
147
  /// Parser for seconds, which can be an integer or have a fractional part
131
- fn parse_seconds_with_fraction(input: &str) -> IResult<&str, f64> {
148
+ fn parse_seconds_with_fraction(input: &[u8]) -> IResult<&[u8], f64> {
132
149
  map_res(
133
- recognize(pair(digit1, opt(preceded(char('.'), digit1)))),
150
+ map_res(
151
+ recognize(pair(digit1, opt(preceded(char('.'), digit1)))),
152
+ as_str,
153
+ ),
134
154
  |s: &str| s.parse::<f64>(),
135
155
  )
136
156
  .parse(input)
137
157
  }
138
158
 
139
159
  /// Parser for one or more whitespace characters (spaces, newlines, tabs, etc.)
140
- fn spacing(input: &str) -> IResult<&str, &str> {
141
- multispace1(input)
160
+ fn spacing(input: &[u8]) -> IResult<&[u8], &[u8]> {
161
+ take_while1(|c: u8| c.is_ascii_whitespace())(input)
142
162
  }
143
163
 
144
164
  #[cfg(test)]
@@ -147,7 +167,7 @@ mod tests {
147
167
 
148
168
  #[test]
149
169
  fn test_comments1() {
150
- let input = "[%eval 123] some text [%clk 12:34:56]";
170
+ let input = b"[%eval 123] some text [%clk 12:34:56]";
151
171
  let result = parse_comments(input);
152
172
  assert!(result.is_ok());
153
173
  let (remaining, parsed) = result.unwrap();
@@ -155,7 +175,7 @@ mod tests {
155
175
  parsed,
156
176
  vec![
157
177
  CommentContent::Tag(ParsedTag::Eval(123.0)),
158
- CommentContent::Text(" some text ".to_string()),
178
+ CommentContent::Text(Cow::from(" some text ")),
159
179
  CommentContent::Tag(ParsedTag::ClkTime {
160
180
  hours: 12,
161
181
  minutes: 34,
@@ -163,12 +183,12 @@ mod tests {
163
183
  })
164
184
  ]
165
185
  );
166
- assert_eq!(remaining, "");
186
+ assert_eq!(remaining, b"");
167
187
  }
168
188
 
169
189
  #[test]
170
190
  fn test_comments2() {
171
- let input = "[%clk 12:34:56] some text ";
191
+ let input = b"[%clk 12:34:56] some text ";
172
192
  let result = parse_comments(input);
173
193
  assert!(result.is_ok());
174
194
  let (remaining, parsed) = result.unwrap();
@@ -180,15 +200,15 @@ mod tests {
180
200
  minutes: 34,
181
201
  seconds: 56.0
182
202
  }),
183
- CommentContent::Text(" some text ".to_string())
203
+ CommentContent::Text(Cow::from(" some text "))
184
204
  ]
185
205
  );
186
- assert_eq!(remaining, "");
206
+ assert_eq!(remaining, b"");
187
207
  }
188
208
 
189
209
  #[test]
190
210
  fn test_comment_fractional_sec() {
191
- let input = "[%clk 12:34:56.0123]";
211
+ let input = b"[%clk 12:34:56.0123]";
192
212
  let result = parse_comments(input);
193
213
  assert!(result.is_ok());
194
214
  let (remaining, parsed) = result.unwrap();
@@ -200,32 +220,32 @@ mod tests {
200
220
  seconds: 56.0123
201
221
  })]
202
222
  );
203
- assert_eq!(remaining, "");
223
+ assert_eq!(remaining, b"");
204
224
  }
205
225
 
206
226
  #[test]
207
227
  fn test_parse_structured_tag_eval() {
208
- let input = "[%eval 123]";
228
+ let input = b"[%eval 123]";
209
229
  let result = parse_structured_tag(input);
210
230
  assert!(result.is_ok());
211
231
  let (remaining, parsed) = result.unwrap();
212
232
  assert_eq!(parsed, ParsedTag::Eval(123.0));
213
- assert_eq!(remaining, "");
233
+ assert_eq!(remaining, b"");
214
234
  }
215
235
 
216
236
  #[test]
217
237
  fn test_parse_structured_tag_eval_mate() {
218
- let input = "[%eval #-3]";
238
+ let input = b"[%eval #-3]";
219
239
  let result = parse_structured_tag(input);
220
240
  assert!(result.is_ok());
221
241
  let (remaining, parsed) = result.unwrap();
222
242
  assert_eq!(parsed, ParsedTag::Mate(-3));
223
- assert_eq!(remaining, "");
243
+ assert_eq!(remaining, b"");
224
244
  }
225
245
 
226
246
  #[test]
227
247
  fn test_parse_structured_tag_clk() {
228
- let input = "[%clk 1:2:3.45]";
248
+ let input = b"[%clk 1:2:3.45]";
229
249
  let result = parse_structured_tag(input);
230
250
  assert!(result.is_ok());
231
251
  let (remaining, parsed) = result.unwrap();
@@ -237,13 +257,13 @@ mod tests {
237
257
  seconds: 3.45
238
258
  }
239
259
  );
240
- assert_eq!(remaining, "");
260
+ assert_eq!(remaining, b"");
241
261
  }
242
262
 
243
263
  // clk expects a particular format: H:M:S - this test ensures incorrect formats fail
244
264
  #[test]
245
265
  fn test_tag_parser_incorrect_clk_value() {
246
- let input = "[%clk 123]"; // Incorrect format for clk time
266
+ let input = b"[%clk 123]"; // Incorrect format for clk time
247
267
  let result = parse_structured_tag(input);
248
268
  assert!(
249
269
  result.is_err(),
@@ -253,7 +273,7 @@ mod tests {
253
273
 
254
274
  #[test]
255
275
  fn test_tag_parser_incorrect_eval_value() {
256
- let input = "[%eval notanumber]";
276
+ let input = b"[%eval notanumber]";
257
277
  let result = parse_structured_tag(input);
258
278
  assert!(
259
279
  result.is_err(),
@@ -263,7 +283,7 @@ mod tests {
263
283
 
264
284
  #[test]
265
285
  fn test_parse_clk_content_correct() {
266
- let input = "clk 12:34:56";
286
+ let input = b"clk 12:34:56";
267
287
  let result = parse_clk_content(input);
268
288
  assert!(result.is_ok());
269
289
  let (remaining, parsed) = result.unwrap();
@@ -275,134 +295,134 @@ mod tests {
275
295
  seconds: 56.0
276
296
  }
277
297
  );
278
- assert_eq!(remaining, "");
298
+ assert_eq!(remaining, b"");
279
299
  }
280
300
 
281
301
  #[test]
282
302
  fn test_parse_clk_content_incorrect_name() {
283
303
  // This tests if parse_clk_content correctly fails if "clk" is not the prefix
284
- let input = "eval 123";
304
+ let input = b"eval 123";
285
305
  let result = parse_clk_content(input);
286
306
  assert!(result.is_err());
287
307
  }
288
308
 
289
309
  #[test]
290
310
  fn test_parse_eval_content_correct_float() {
291
- let input = "eval -42.5";
311
+ let input = b"eval -42.5";
292
312
  let result = parse_eval_content(input);
293
313
  assert!(result.is_ok());
294
314
  let (remaining, parsed) = result.unwrap();
295
315
  assert_eq!(parsed, ParsedTag::Eval(-42.5));
296
- assert_eq!(remaining, "");
316
+ assert_eq!(remaining, b"");
297
317
  }
298
318
 
299
319
  #[test]
300
320
  fn test_parse_eval_content_correct_mate() {
301
- let input = "eval #5";
321
+ let input = b"eval #5";
302
322
  let result = parse_eval_content(input);
303
323
  assert!(result.is_ok());
304
324
  let (remaining, parsed) = result.unwrap();
305
325
  assert_eq!(parsed, ParsedTag::Mate(5));
306
- assert_eq!(remaining, "");
326
+ assert_eq!(remaining, b"");
307
327
  }
308
328
 
309
329
  #[test]
310
330
  fn test_parse_signed_float_positive() {
311
- let input = "123.45";
331
+ let input = b"123.45";
312
332
  let result = parse_signed_float(input);
313
333
  assert!(result.is_ok());
314
334
  let (remaining, parsed) = result.unwrap();
315
335
  assert_eq!(parsed, 123.45);
316
- assert_eq!(remaining, "");
336
+ assert_eq!(remaining, b"");
317
337
  }
318
338
 
319
339
  #[test]
320
340
  fn test_parse_signed_float_negative() {
321
- let input = "-0.5";
341
+ let input = b"-0.5";
322
342
  let result = parse_signed_float(input);
323
343
  assert!(result.is_ok());
324
344
  let (remaining, parsed) = result.unwrap();
325
345
  assert_eq!(parsed, -0.5);
326
- assert_eq!(remaining, "");
346
+ assert_eq!(remaining, b"");
327
347
  }
328
348
 
329
349
  #[test]
330
350
  fn test_parse_mate_value_positive() {
331
- let input = "#7";
351
+ let input = b"#7";
332
352
  let result = parse_mate_value(input);
333
353
  assert!(result.is_ok());
334
354
  let (remaining, parsed) = result.unwrap();
335
355
  assert_eq!(parsed, 7);
336
- assert_eq!(remaining, "");
356
+ assert_eq!(remaining, b"");
337
357
  }
338
358
 
339
359
  #[test]
340
360
  fn test_parse_mate_value_negative() {
341
- let input = "#-2";
361
+ let input = b"#-2";
342
362
  let result = parse_mate_value(input);
343
363
  assert!(result.is_ok());
344
364
  let (remaining, parsed) = result.unwrap();
345
365
  assert_eq!(parsed, -2);
346
- assert_eq!(remaining, "");
366
+ assert_eq!(remaining, b"");
347
367
  }
348
368
 
349
369
  #[test]
350
370
  fn test_parse_hms_time_simple() {
351
- let input = "12:34:56";
371
+ let input = b"12:34:56";
352
372
  let result = parse_hms_time(input);
353
373
  assert!(result.is_ok());
354
374
  let (remaining, parsed) = result.unwrap();
355
375
  assert_eq!(parsed, (12, 34, 56.0));
356
- assert_eq!(remaining, "");
376
+ assert_eq!(remaining, b"");
357
377
  }
358
378
 
359
379
  #[test]
360
380
  fn test_parse_hms_time_fractional() {
361
- let input = "01:02:03.123";
381
+ let input = b"01:02:03.123";
362
382
  let result = parse_hms_time(input);
363
383
  assert!(result.is_ok());
364
384
  let (remaining, parsed) = result.unwrap();
365
385
  assert_eq!(parsed, (1, 2, 3.123));
366
- assert_eq!(remaining, "");
386
+ assert_eq!(remaining, b"");
367
387
  }
368
388
 
369
389
  #[test]
370
390
  fn test_parse_hms_time_invalid_char_in_hour() {
371
- let input = "1a:00:00";
391
+ let input = b"1a:00:00";
372
392
  let result = parse_hms_time(input);
373
393
  assert!(result.is_err());
374
394
  }
375
395
 
376
396
  #[test]
377
397
  fn test_parse_hms_time_missing_minutes() {
378
- let input = "1::00";
398
+ let input = b"1::00";
379
399
  let result = parse_hms_time(input); // digit1 for minutes will fail on ':'
380
400
  assert!(result.is_err());
381
401
  }
382
402
 
383
403
  #[test]
384
404
  fn test_parse_seconds_with_fraction_integer() {
385
- let input = "42";
405
+ let input = b"42";
386
406
  let result = parse_seconds_with_fraction(input);
387
407
  assert!(result.is_ok());
388
408
  let (remaining, parsed) = result.unwrap();
389
409
  assert_eq!(parsed, 42.0);
390
- assert_eq!(remaining, "");
410
+ assert_eq!(remaining, b"");
391
411
  }
392
412
 
393
413
  #[test]
394
414
  fn test_parse_seconds_with_fraction_decimal() {
395
- let input = "3.141";
415
+ let input = b"3.141";
396
416
  let result = parse_seconds_with_fraction(input);
397
417
  assert!(result.is_ok());
398
418
  let (remaining, parsed) = result.unwrap();
399
419
  assert_eq!(parsed, 3.141);
400
- assert_eq!(remaining, "");
420
+ assert_eq!(remaining, b"");
401
421
  }
402
422
 
403
423
  #[test]
404
424
  fn test_unknown_tag_is_parsed_as_text() {
405
- let input = "[%timestamp 12345] some text";
425
+ let input = b"[%timestamp 12345] some text";
406
426
  let result = parse_comments(input);
407
427
  assert!(result.is_ok());
408
428
  let (remaining, parsed) = result.unwrap();
@@ -410,16 +430,16 @@ mod tests {
410
430
  parsed,
411
431
  vec![
412
432
  // The whole unknown tag becomes a text element
413
- CommentContent::Text("[%timestamp 12345]".to_string()),
414
- CommentContent::Text(" some text".to_string()) // Note: leading space is part of the text
433
+ CommentContent::Text(Cow::from("[%timestamp 12345]")),
434
+ CommentContent::Text(Cow::from(" some text")) // Note: leading space is part of the text
415
435
  ]
416
436
  );
417
- assert_eq!(remaining, "");
437
+ assert_eq!(remaining, b"");
418
438
  }
419
439
 
420
440
  #[test]
421
441
  fn test_clk_tag_with_newline_spacing() {
422
- let input = "[%clk\n0:09:36.2]";
442
+ let input = b"[%clk\n0:09:36.2]";
423
443
  let result = parse_comments(input);
424
444
  assert!(result.is_ok());
425
445
  let (remaining, parsed) = result.unwrap();
@@ -431,29 +451,29 @@ mod tests {
431
451
  seconds: 36.2
432
452
  })]
433
453
  );
434
- assert_eq!(remaining, "");
454
+ assert_eq!(remaining, b"");
435
455
  }
436
456
 
437
457
  #[test]
438
458
  fn test_eval_tag_with_newline_spacing() {
439
- let input = "[%eval\n-0.5]";
459
+ let input = b"[%eval\n-0.5]";
440
460
  let result = parse_comments(input);
441
461
  assert!(result.is_ok());
442
462
  let (remaining, parsed) = result.unwrap();
443
463
  assert_eq!(parsed, vec![CommentContent::Tag(ParsedTag::Eval(-0.5))]);
444
- assert_eq!(remaining, "");
464
+ assert_eq!(remaining, b"");
445
465
  }
446
466
 
447
467
  #[test]
448
468
  fn test_text_containing_brackets_not_tag() {
449
- let input = "Text with [normal brackets] and then [%clk 1:2:3]";
469
+ let input = b"Text with [normal brackets] and then [%clk 1:2:3]";
450
470
  let result = parse_comments(input);
451
471
  assert!(result.is_ok());
452
472
  let (remaining, parsed) = result.unwrap();
453
473
  assert_eq!(
454
474
  parsed,
455
475
  vec![
456
- CommentContent::Text("Text with [normal brackets] and then ".to_string()),
476
+ CommentContent::Text(Cow::from("Text with [normal brackets] and then ")),
457
477
  CommentContent::Tag(ParsedTag::ClkTime {
458
478
  hours: 1,
459
479
  minutes: 2,
@@ -461,6 +481,6 @@ mod tests {
461
481
  })
462
482
  ]
463
483
  );
464
- assert_eq!(remaining, "");
484
+ assert_eq!(remaining, b"");
465
485
  }
466
486
  }
@@ -24,6 +24,7 @@ print(extractor.valid_moves)
24
24
  print(extractor.evals)
25
25
  print(extractor.clock_times)
26
26
  print(extractor.outcome)
27
+ assert extractor.position_status is not None
27
28
  print(extractor.position_status.is_checkmate)
28
29
  print(extractor.position_status.is_stalemate)
29
30
  print(extractor.position_status.is_game_over)
@@ -1,13 +1,14 @@
1
1
  use crate::comment_parsing::{parse_comments, CommentContent, ParsedTag};
2
2
  use arrow_array::{Array, LargeStringArray, StringArray};
3
- use pgn_reader::{BufferedReader, RawComment, RawHeader, SanPlus, Skip, Visitor};
3
+ use pgn_reader::{KnownOutcome, Outcome, RawComment, RawTag, Reader, SanPlus, Skip, Visitor};
4
4
  use pyo3::prelude::*;
5
5
  use pyo3_arrow::PyChunkedArray;
6
6
  use rayon::prelude::*;
7
7
  use rayon::ThreadPoolBuilder;
8
8
  use shakmaty::Color;
9
- use shakmaty::{uci::UciMove, Chess, Outcome, Position, Role, Square};
9
+ use shakmaty::{uci::UciMove, Chess, Position, Role, Square};
10
10
  use std::io::Cursor;
11
+ use std::ops::ControlFlow;
11
12
 
12
13
  mod comment_parsing;
13
14
 
@@ -194,15 +195,29 @@ impl MoveExtractor {
194
195
  }
195
196
 
196
197
  impl Visitor for MoveExtractor {
197
- type Result = bool;
198
+ type Tags = Vec<(String, String)>;
199
+ type Movetext = ();
200
+ type Output = bool;
198
201
 
199
- fn header(&mut self, key: &[u8], value: RawHeader<'_>) {
202
+ fn begin_tags(&mut self) -> ControlFlow<Self::Output, Self::Tags> {
203
+ self.headers.clear();
204
+ ControlFlow::Continue(Vec::with_capacity(10))
205
+ }
206
+
207
+ fn tag(
208
+ &mut self,
209
+ tags: &mut Self::Tags,
210
+ key: &[u8],
211
+ value: RawTag<'_>,
212
+ ) -> ControlFlow<Self::Output> {
200
213
  let key_str = String::from_utf8_lossy(key).into_owned();
201
214
  let value_str = String::from_utf8_lossy(value.as_bytes()).into_owned();
202
- self.headers.push((key_str, value_str));
215
+ tags.push((key_str, value_str));
216
+ ControlFlow::Continue(())
203
217
  }
204
218
 
205
- fn begin_game(&mut self) {
219
+ fn begin_movetext(&mut self, tags: Self::Tags) -> ControlFlow<Self::Output, Self::Movetext> {
220
+ self.headers = tags;
206
221
  self.moves.clear();
207
222
  self.pos = Chess::default();
208
223
  self.valid_moves = true;
@@ -211,14 +226,19 @@ impl Visitor for MoveExtractor {
211
226
  self.clock_times.clear();
212
227
 
213
228
  self.push_castling_bitboards();
229
+ ControlFlow::Continue(())
214
230
  }
215
231
 
216
- fn san(&mut self, san_plus: SanPlus) {
232
+ fn san(
233
+ &mut self,
234
+ _movetext: &mut Self::Movetext,
235
+ san_plus: SanPlus,
236
+ ) -> ControlFlow<Self::Output> {
217
237
  if self.valid_moves {
218
238
  match san_plus.san.to_move(&self.pos) {
219
239
  Ok(m) => {
220
- self.pos.play_unchecked(&m);
221
- let uci_move_obj = UciMove::from_standard(&m);
240
+ self.pos.play_unchecked(m);
241
+ let uci_move_obj = UciMove::from_standard(m);
222
242
 
223
243
  match uci_move_obj {
224
244
  UciMove::Normal {
@@ -249,15 +269,19 @@ impl Visitor for MoveExtractor {
249
269
  }
250
270
  }
251
271
  }
272
+ ControlFlow::Continue(())
252
273
  }
253
274
 
254
- fn comment(&mut self, _comment: RawComment<'_>) {
255
- let comment = String::from_utf8_lossy(_comment.as_bytes()).into_owned();
256
- match parse_comments(&comment) {
275
+ fn comment(
276
+ &mut self,
277
+ _movetext: &mut Self::Movetext,
278
+ _comment: RawComment<'_>,
279
+ ) -> ControlFlow<Self::Output> {
280
+ match parse_comments(_comment.as_bytes()) {
257
281
  Ok((remaining_input, parsed_comments)) => {
258
282
  if !remaining_input.is_empty() {
259
283
  eprintln!("Unparsed remaining input: {:?}", remaining_input);
260
- return;
284
+ return ControlFlow::Continue(());
261
285
  }
262
286
  let mut eval_encountered = false;
263
287
  let mut clk_time_encountered = false;
@@ -318,28 +342,40 @@ impl Visitor for MoveExtractor {
318
342
  eprintln!("Error parsing comment: {:?}", e);
319
343
  }
320
344
  }
345
+ ControlFlow::Continue(())
321
346
  }
322
347
 
323
- fn begin_variation(&mut self) -> Skip {
324
- Skip(true) // stay in the mainline
348
+ fn begin_variation(
349
+ &mut self,
350
+ _movetext: &mut Self::Movetext,
351
+ ) -> ControlFlow<Self::Output, Skip> {
352
+ ControlFlow::Continue(Skip(true)) // stay in the mainline
325
353
  }
326
354
 
327
- fn outcome(&mut self, _outcome: Option<Outcome>) {
328
- self.outcome = _outcome.map(|o| match o {
329
- Outcome::Decisive { winner } => format!("{:?}", winner),
330
- Outcome::Draw => "Draw".to_string(),
355
+ fn outcome(
356
+ &mut self,
357
+ _movetext: &mut Self::Movetext,
358
+ _outcome: Outcome,
359
+ ) -> ControlFlow<Self::Output> {
360
+ self.outcome = Some(match _outcome {
361
+ Outcome::Known(known) => match known {
362
+ KnownOutcome::Decisive { winner } => format!("{:?}", winner),
363
+ KnownOutcome::Draw => "Draw".to_string(),
364
+ },
365
+ Outcome::Unknown => "Unknown".to_string(),
331
366
  });
332
367
  self.update_position_status();
368
+ ControlFlow::Continue(())
333
369
  }
334
370
 
335
- fn end_game(&mut self) -> Self::Result {
371
+ fn end_game(&mut self, _movetext: Self::Movetext) -> Self::Output {
336
372
  self.valid_moves
337
373
  }
338
374
  }
339
375
 
340
376
  // --- Native Rust versions (no PyResult) ---
341
377
  pub fn parse_single_game_native(pgn: &str) -> Result<MoveExtractor, String> {
342
- let mut reader = BufferedReader::new(Cursor::new(pgn));
378
+ let mut reader = Reader::new(Cursor::new(pgn));
343
379
  let mut extractor = MoveExtractor::new();
344
380
  match reader.read_game(&mut extractor) {
345
381
  Ok(Some(_)) => Ok(extractor),
@@ -352,7 +388,7 @@ pub fn parse_multiple_games_native(
352
388
  pgns: &Vec<String>,
353
389
  num_threads: Option<usize>,
354
390
  ) -> Result<Vec<MoveExtractor>, String> {
355
- let num_threads = num_threads.unwrap_or_else(|| num_cpus::get());
391
+ let num_threads = num_threads.unwrap_or_else(num_cpus::get);
356
392
 
357
393
  // Build a custom Rayon thread pool with the desired number of threads
358
394
  let thread_pool = ThreadPoolBuilder::new()
@@ -381,7 +417,7 @@ fn _parse_game_moves_from_arrow_chunks_native(
381
417
  pgn_chunked_array: &PyChunkedArray,
382
418
  num_threads: Option<usize>,
383
419
  ) -> Result<Vec<MoveExtractor>, String> {
384
- let num_threads = num_threads.unwrap_or_else(|| num_cpus::get());
420
+ let num_threads = num_threads.unwrap_or_else(num_cpus::get);
385
421
  let thread_pool = ThreadPoolBuilder::new()
386
422
  .num_threads(num_threads)
387
423
  .build()
@@ -427,15 +463,14 @@ fn _parse_game_moves_from_arrow_chunks_native(
427
463
  #[pyfunction]
428
464
  /// Parses a single PGN game string.
429
465
  fn parse_game(pgn: &str) -> PyResult<MoveExtractor> {
430
- parse_single_game_native(pgn).map_err(|err| pyo3::exceptions::PyValueError::new_err(err))
466
+ parse_single_game_native(pgn).map_err(pyo3::exceptions::PyValueError::new_err)
431
467
  }
432
468
 
433
469
  /// In parallel, parse a set of games
434
470
  #[pyfunction]
435
471
  #[pyo3(signature = (pgns, num_threads=None))]
436
472
  fn parse_games(pgns: Vec<String>, num_threads: Option<usize>) -> PyResult<Vec<MoveExtractor>> {
437
- parse_multiple_games_native(&pgns, num_threads)
438
- .map_err(|err| pyo3::exceptions::PyValueError::new_err(err))
473
+ parse_multiple_games_native(&pgns, num_threads).map_err(pyo3::exceptions::PyValueError::new_err)
439
474
  }
440
475
 
441
476
  #[pyfunction]
@@ -445,7 +480,7 @@ fn parse_game_moves_arrow_chunked_array(
445
480
  num_threads: Option<usize>,
446
481
  ) -> PyResult<Vec<MoveExtractor>> {
447
482
  _parse_game_moves_from_arrow_chunks_native(&pgn_chunked_array, num_threads)
448
- .map_err(|err| pyo3::exceptions::PyValueError::new_err(err))
483
+ .map_err(pyo3::exceptions::PyValueError::new_err)
449
484
  }
450
485
 
451
486
  /// Parser for chess PGN notation
@@ -1,54 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: rust_pgn_reader_python_binding
3
- Version: 3.1.0
4
- Classifier: Programming Language :: Rust
5
- Classifier: Programming Language :: Python :: Implementation :: CPython
6
- Classifier: Programming Language :: Python :: Implementation :: PyPy
7
- Requires-Python: >=3.8
8
- Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
9
-
10
- # rust_pgn_reader_python_binding
11
- ## Fast PGN parsing bindings for Python
12
- This project adds Python bindings to [rust-pgn-reader](https://github.com/niklasf/rust-pgn-reader). In addition, it also parses and extracts [%clk ..] and [%eval ..] tags from comments.
13
-
14
- ## Installing
15
- `pip install rust_pgn_reader_python_binding`
16
-
17
- ## Benchmarks
18
- Below are some benchmarks on Lichess's 2013-07 chess games (293,459 games) on an 7800X3D.
19
-
20
- | Parser | File format | Time |
21
- |----------------------------------------------------------------------------|-------------|--------|
22
- | [rust-pgn-reader](https://github.com/niklasf/rust-pgn-reader/tree/master) | PGN | 1s |
23
- | rust_pgn_reader_python_binding | PGN | 4.7s |
24
- | rust_pgn_reader_python_binding, parse_game (single_threaded) | parquet | 3.3s |
25
- | rust_pgn_reader_python_binding, parse_games (multithreaded) | parquet | 0.5s |
26
- | rust_pgn_reader_python_binding, parse_game_moves_arrow_chunked_array (multithreaded) | parquet | 0.35s |
27
- | [chess-library](https://github.com/Disservin/chess-library) | PGN | 2s |
28
- | [python-chess](https://github.com/niklasf/python-chess) | PGN | 3+ min |
29
-
30
- To replicate, download `2013-07-train-00000-of-00001.parquet` and then run:
31
-
32
- `python bench_parquet.py` (single-threaded parse_game)
33
-
34
- `python bench_parquet_parallel.py` (multithreaded parse_games)
35
-
36
- `python bench_parquet_arrow.py` (multithreaded parse_game_moves_arrow_chunked_array)
37
-
38
- ## Building
39
- `maturin develop`
40
-
41
- `maturin develop --release`
42
-
43
- For a more thorough tutorial, follow https://lukesalamone.github.io/posts/how-to-create-rust-python-bindings/
44
-
45
- ## Profiling
46
- `py-spy record -s -F -f speedscope --output profile.speedscope -- python ./src/bench_parquet.py`
47
-
48
- Linux/WSL-only:
49
- `py-spy record -s -F -n -f speedscope --output profile.speedscope -- python ./src/bench_parquet.py`
50
-
51
- ## Testing
52
- `cargo test`
53
-
54
- `python -m unittest src/test.py`