From 50f88ef753a14ffd76f9502a988f8dd8b65d9025 Mon Sep 17 00:00:00 2001 From: LQ63 Date: Wed, 8 Apr 2026 08:40:27 +0200 Subject: [PATCH] feat(io): FastParse FEN Added Shared Parser support --- .../io/fen/FenParserCombinators.scala | 33 +++--------------- .../nowchess/io/fen/FenParserFastParse.scala | 34 +------------------ 2 files changed, 5 insertions(+), 62 deletions(-) diff --git a/modules/io/src/main/scala/de/nowchess/io/fen/FenParserCombinators.scala b/modules/io/src/main/scala/de/nowchess/io/fen/FenParserCombinators.scala index b1a4b8f..a8e77ca 100644 --- a/modules/io/src/main/scala/de/nowchess/io/fen/FenParserCombinators.scala +++ b/modules/io/src/main/scala/de/nowchess/io/fen/FenParserCombinators.scala @@ -4,6 +4,7 @@ import de.nowchess.api.board.* import de.nowchess.api.game.GameContext import de.nowchess.io.GameContextImport import scala.util.parsing.combinator.RegexParsers +import FenParserSupport.* object FenParserCombinators extends RegexParsers with GameContextImport: @@ -11,15 +12,6 @@ object FenParserCombinators extends RegexParsers with GameContextImport: // ── Piece character ────────────────────────────────────────────────────── - private val charToPieceType: Map[Char, PieceType] = Map( - 'p' -> PieceType.Pawn, - 'r' -> PieceType.Rook, - 'n' -> PieceType.Knight, - 'b' -> PieceType.Bishop, - 'q' -> PieceType.Queen, - 'k' -> PieceType.King - ) - private def pieceChar: Parser[Piece] = "[prnbqkPRNBQK]".r ^^ { s => val c = s.head @@ -32,11 +24,6 @@ object FenParserCombinators extends RegexParsers with GameContextImport: // ── Rank parser ────────────────────────────────────────────────────────── - /** Parse a sequence of piece-chars and empty-counts, returning tagged tokens. */ - private sealed trait RankToken - private case class PieceToken(piece: Piece) extends RankToken - private case class EmptyToken(count: Int) extends RankToken - private def rankToken: Parser[RankToken] = pieceChar ^^ PieceToken.apply | emptyCount ^^ EmptyToken.apply @@ -46,21 +33,9 @@ object FenParserCombinators extends RegexParsers with GameContextImport: * Fails if total file count != 8 or any piece placement exceeds board bounds. */ private def rankParser(rank: Rank): Parser[List[(Square, Piece)]] = rankTokens >> { tokens => - val result = tokens.foldLeft(Option((List.empty[(Square, Piece)], 0))): - case (None, _) => None - case (Some((acc, fileIdx)), PieceToken(piece)) => - if fileIdx > 7 then None - else - val sq = Square(File.values(fileIdx), rank) - Some((acc :+ (sq -> piece), fileIdx + 1)) - case (Some((acc, fileIdx)), EmptyToken(n)) => - val next = fileIdx + n - if next > 8 then None - else Some((acc, next)) - result match - case Some((squares, 8)) => success(squares) - case Some((_, total)) => failure(s"Rank $rank has $total files, expected 8") - case None => failure(s"Rank $rank exceeds board width") + buildSquares(rank, tokens) match + case Some(squares) => success(squares) + case None => failure(s"Rank $rank is invalid") } // ── Board parser ───────────────────────────────────────────────────────── diff --git a/modules/io/src/main/scala/de/nowchess/io/fen/FenParserFastParse.scala b/modules/io/src/main/scala/de/nowchess/io/fen/FenParserFastParse.scala index f27a441..df129c3 100644 --- a/modules/io/src/main/scala/de/nowchess/io/fen/FenParserFastParse.scala +++ b/modules/io/src/main/scala/de/nowchess/io/fen/FenParserFastParse.scala @@ -5,26 +5,10 @@ import fastparse.NoWhitespace.* import de.nowchess.api.board.* import de.nowchess.api.game.GameContext import de.nowchess.io.GameContextImport +import FenParserSupport.* object FenParserFastParse extends GameContextImport: - // ── Rank token ADT ────────────────────────────────────────────────────── - - private sealed trait RankToken - private case class PieceToken(piece: Piece) extends RankToken - private case class EmptyToken(count: Int) extends RankToken - - // ── Char-to-piece mapping ──────────────────────────────────────────────── - - private val charToPieceType: Map[Char, PieceType] = Map( - 'p' -> PieceType.Pawn, - 'r' -> PieceType.Rook, - 'n' -> PieceType.Knight, - 'b' -> PieceType.Bishop, - 'q' -> PieceType.Queen, - 'k' -> PieceType.King - ) - // ── Low-level parsers ──────────────────────────────────────────────────── private def pieceChar(using P[Any]): P[Piece] = @@ -40,22 +24,6 @@ object FenParserFastParse extends GameContextImport: private def rankToken(using P[Any]): P[RankToken] = pieceChar.map(PieceToken.apply) | emptyCount.map(EmptyToken.apply) - // ── Rank validation helper ─────────────────────────────────────────────── - - private def buildSquares(rank: Rank, tokens: Seq[RankToken]): Option[List[(Square, Piece)]] = - tokens.foldLeft(Option((List.empty[(Square, Piece)], 0))): - case (None, _) => None - case (Some((acc, fileIdx)), PieceToken(piece)) => - if fileIdx > 7 then None - else - val sq = Square(File.values(fileIdx), rank) - Some((acc :+ (sq -> piece), fileIdx + 1)) - case (Some((acc, fileIdx)), EmptyToken(n)) => - val next = fileIdx + n - if next > 8 then None - else Some((acc, next)) - .flatMap { case (squares, total) => if total == 8 then Some(squares) else None } - // ── Rank parser ────────────────────────────────────────────────────────── private def rankParser(rank: Rank)(using P[Any]): P[List[(Square, Piece)]] =