refactor(io): implement FenParser.parseFen returning Either with detailed error messages
Update parseFen to return Either[String, GameContext] with specific error messages for each validation failure: - Invalid parts count: reports expected 6 fields - Invalid board: clear message about board position - Invalid color: explains expected 'w' or 'b' - Invalid castling, en passant, and move counts with clear descriptions Simplify importGameContext to delegate directly to parseFen. Keep helper methods (parseColor, parseCastling, parseEnPassant, etc.) returning Option as before. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,18 +7,20 @@ import de.nowchess.io.GameContextImport
|
|||||||
object FenParser extends GameContextImport:
|
object FenParser extends GameContextImport:
|
||||||
|
|
||||||
/** Parse a complete FEN string into a GameContext.
|
/** Parse a complete FEN string into a GameContext.
|
||||||
* Returns None if the format is invalid. */
|
* Returns Left with error message if the format is invalid. */
|
||||||
def parseFen(fen: String): Option[GameContext] =
|
def parseFen(fen: String): Either[String, GameContext] =
|
||||||
val parts = fen.trim.split("\\s+")
|
val parts = fen.trim.split("\\s+")
|
||||||
Option.when(parts.length == 6)(parts).flatMap: parts =>
|
if parts.length != 6 then
|
||||||
|
Left(s"Invalid FEN: expected 6 space-separated fields, got ${parts.length}")
|
||||||
|
else
|
||||||
for
|
for
|
||||||
board <- parseBoard(parts(0))
|
board <- parseBoard(parts(0)).toRight("Invalid FEN: invalid board position")
|
||||||
activeColor <- parseColor(parts(1))
|
activeColor <- parseColor(parts(1)).toRight("Invalid FEN: invalid active color (expected 'w' or 'b')")
|
||||||
castlingRights <- parseCastling(parts(2))
|
castlingRights <- parseCastling(parts(2)).toRight("Invalid FEN: invalid castling rights")
|
||||||
enPassant <- parseEnPassant(parts(3))
|
enPassant <- parseEnPassant(parts(3)).toRight("Invalid FEN: invalid en passant square")
|
||||||
halfMoveClock <- parts(4).toIntOption
|
halfMoveClock <- parts(4).toIntOption.toRight("Invalid FEN: invalid half-move clock (expected integer)")
|
||||||
fullMoveNumber <- parts(5).toIntOption
|
fullMoveNumber <- parts(5).toIntOption.toRight("Invalid FEN: invalid full move number (expected integer)")
|
||||||
if halfMoveClock >= 0 && fullMoveNumber >= 1
|
_ <- Either.cond(halfMoveClock >= 0 && fullMoveNumber >= 1, (), "Invalid FEN: invalid move counts")
|
||||||
yield GameContext(
|
yield GameContext(
|
||||||
board = board,
|
board = board,
|
||||||
turn = activeColor,
|
turn = activeColor,
|
||||||
@@ -29,7 +31,7 @@ object FenParser extends GameContextImport:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def importGameContext(input: String): Either[String, GameContext] =
|
def importGameContext(input: String): Either[String, GameContext] =
|
||||||
parseFen(input).toRight("Invalid FEN string")
|
parseFen(input)
|
||||||
|
|
||||||
/** Parse active color ("w" or "b"). */
|
/** Parse active color ("w" or "b"). */
|
||||||
private def parseColor(s: String): Option[Color] =
|
private def parseColor(s: String): Option[Color] =
|
||||||
|
|||||||
Reference in New Issue
Block a user