mirror of
https://github.com/lichess-org/dartchess.git
synced 2026-05-26 13:51:01 +00:00
Merge pull request #54 from HaonRekcef/lazy-pgn
Add lazy parsing for multi-game PGN strings + tests
This commit is contained in:
+48
-1
@@ -138,6 +138,29 @@ class PgnGame<T extends PgnNodeData> {
|
||||
return games;
|
||||
}
|
||||
|
||||
/// Parse a multi-game PGN string lazily.
|
||||
///
|
||||
/// Returns a list of [PgnLazyGame] where only the headers are parsed and the move tree is parsed on-demand when [toPgnGame] is called.
|
||||
static List<PgnLazyGame> parseMultiGameLazy(String pgn,
|
||||
{PgnHeaders Function() initHeaders = defaultHeaders}) {
|
||||
final multiGamePgnSplit = RegExp(r'\n\s+(?=\[)');
|
||||
final List<PgnLazyGame> games = [];
|
||||
final pgnGames = pgn.split(multiGamePgnSplit);
|
||||
|
||||
for (final pgnGame in pgnGames) {
|
||||
PgnHeaders? extractedHeaders;
|
||||
_PgnParser((game) {
|
||||
extractedHeaders = game.headers;
|
||||
}, initHeaders, parseMoves: false)
|
||||
.parse(pgnGame);
|
||||
|
||||
if (extractedHeaders != null) {
|
||||
games.add(PgnLazyGame(headers: extractedHeaders!, rawPgn: pgnGame));
|
||||
}
|
||||
}
|
||||
return games;
|
||||
}
|
||||
|
||||
/// Create a [Position] for a Variant from the headers.
|
||||
///
|
||||
/// Headers can include an optional 'Variant' and 'Fen' key.
|
||||
@@ -674,6 +697,7 @@ bool _isCommentLine(String line) => line.startsWith('%');
|
||||
/// A class to read a string and create a [PgnGame]
|
||||
class _PgnParser {
|
||||
List<String> _lineBuf = [];
|
||||
final bool parseMoves;
|
||||
late bool _found;
|
||||
late _ParserState _state = _ParserState.pre;
|
||||
late PgnHeaders _gameHeaders;
|
||||
@@ -688,7 +712,7 @@ class _PgnParser {
|
||||
/// Function to create the headers
|
||||
final PgnHeaders Function() initHeaders;
|
||||
|
||||
_PgnParser(this.emitGame, this.initHeaders) {
|
||||
_PgnParser(this.emitGame, this.initHeaders, {this.parseMoves = true}) {
|
||||
_resetGame();
|
||||
_state = _ParserState.bom;
|
||||
}
|
||||
@@ -784,6 +808,9 @@ class _PgnParser {
|
||||
|
||||
case _ParserState.moves:
|
||||
{
|
||||
if (!parseMoves) {
|
||||
return;
|
||||
}
|
||||
if (freshLine) {
|
||||
if (_isWhitespace(line) || _isCommentLine(line)) return;
|
||||
}
|
||||
@@ -854,6 +881,9 @@ class _PgnParser {
|
||||
|
||||
case _ParserState.comment:
|
||||
{
|
||||
if (!parseMoves) {
|
||||
return;
|
||||
}
|
||||
final closeIndex = line.indexOf('}');
|
||||
if (closeIndex == -1) {
|
||||
_commentBuf.add(line);
|
||||
@@ -913,3 +943,20 @@ String _makeClk(Duration duration) {
|
||||
intVal.toString().padLeft(2, '0'); // get the decimal part of seconds
|
||||
return '$hours:${minutes.toString().padLeft(2, "0")}:$dec$frac';
|
||||
}
|
||||
|
||||
/// Parse a multi-game PGN string lazily.
|
||||
///
|
||||
/// Returns a list of [PgnLazyGame], where only the tags/headers fall
|
||||
/// under initial evaluation. You can call [toPgnGame()] on an element
|
||||
/// to evaluate the move tree on-demand.
|
||||
class PgnLazyGame {
|
||||
const PgnLazyGame({
|
||||
required this.headers,
|
||||
required this.rawPgn,
|
||||
});
|
||||
|
||||
final PgnHeaders headers;
|
||||
final String rawPgn;
|
||||
|
||||
PgnGame<PgnNodeData> toPgnGame() => PgnGame.parsePgn(rawPgn);
|
||||
}
|
||||
|
||||
@@ -393,6 +393,33 @@ the players are also trying to learn as much as possible about the opponent's pr
|
||||
expect(games.length, 1);
|
||||
});
|
||||
|
||||
test('parseMultiGameLazy - parses headers without moves', () {
|
||||
final String data =
|
||||
File('./data/kasparov-deep-blue-1997.pgn').readAsStringSync();
|
||||
final List<PgnLazyGame> lazyGames = PgnGame.parseMultiGameLazy(data);
|
||||
|
||||
expect(lazyGames.length, 6);
|
||||
|
||||
// Verify headers were extracted
|
||||
expect(lazyGames[0].headers['Event'], 'IBM Man-Machine, New York USA');
|
||||
expect(lazyGames[0].headers['White'], 'Garry Kasparov');
|
||||
expect(lazyGames[0].headers['Black'], 'Deep Blue (Computer)');
|
||||
});
|
||||
|
||||
test('parseMultiGameLazy - toPgnGame parses move tree on-demand', () {
|
||||
final String data =
|
||||
File('./data/kasparov-deep-blue-1997.pgn').readAsStringSync();
|
||||
final List<PgnLazyGame> lazyGames = PgnGame.parseMultiGameLazy(data);
|
||||
|
||||
// Convert the first lazy game to a full PgnGame
|
||||
final fullGame = lazyGames[0].toPgnGame();
|
||||
|
||||
// Verify moves were successfully built
|
||||
expect(fullGame.headers['White'], 'Garry Kasparov');
|
||||
final moves = fullGame.moves.mainline().toList();
|
||||
expect(moves.first.san, 'Nf3');
|
||||
});
|
||||
|
||||
test('crazyhouse from prod', () {
|
||||
final game = PgnGame.parsePgn(PgnFixtures.crazyhouseFromProd);
|
||||
expect(game.moves.mainline().length, 49);
|
||||
|
||||
Reference in New Issue
Block a user