A FEN (Forsyth-Edwards Notation) parser, validator, and board renderer for the command line, written in C with no dependencies beyond the standard library.
FEN is the standard way chess positions get passed between engines, GUIs, and
databases (rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1 is
"the position after 1.e4"). Most tools parse FEN loosely and trust the input.
fenlab instead treats it as a real grammar to validate: rank lengths, legal
piece counts, exactly one king per side, no pawns on the back rank, castling
rights that are actually backed by a king and rook on their home squares, and
an en passant square that's on the right rank. Then it proves the parse was
lossless by serializing the position straight back into byte-identical FEN.
Requires a C11 compiler (cc/gcc) and make. No other dependencies.
make # builds ./fenlab
make test # builds and runs tests/test_fen (24 checks)./fenlab --startposVALID FEN
8 | ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 | ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
6 | · · · · · · · ·
5 | · · · · · · · ·
4 | · · · · · · · ·
3 | · · · · · · · ·
2 | ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙
1 | ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
a b c d e f g h
side to move : white
castling rights: KQkq
en passant : -
halfmove clock : 0
fullmove number: 1
material : white 39, black 39 (excludes kings)
round-trip FEN : rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
Feed it any FEN (as one quoted string or as bare words — the shell splits it
either way, fenlab rejoins them):
./fenlab rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1VALID FEN
8 | ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 | ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
6 | · · · · · · · ·
5 | · · · · · · · ·
4 | · · · · ♙ · · ·
3 | · · · · · · · ·
2 | ♙ ♙ ♙ ♙ · ♙ ♙ ♙
1 | ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
a b c d e f g h
side to move : black
castling rights: KQkq
en passant : e3
halfmove clock : 0
fullmove number: 1
material : white 39, black 39 (excludes kings)
round-trip FEN : rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1
Use --ascii for plain-letter rendering instead of Unicode glyphs (useful in
terminals/fonts without chess symbols). Invalid input is rejected with a
specific reason and a non-zero exit code:
$ ./fenlab "8/8/8/8/8/8/8/8 w - - 0 1"
INVALID: position must have exactly one white king (found 0)src/fen.csplits the FEN into its (up to six) whitespace-separated fields. Piece placement is parsed rank by rank, validating that digits represent a 1-8 empty-square run and every rank sums to exactly 8 squares. Missing trailing fields fall back to standard defaults (w,-,-,0,1), so a bare piece-placement string is also accepted.- After parsing,
validate_rules()checks chess-specific invariants: exactly one king per side, no more than 16 pieces or 8 pawns per side, no pawns on rank 1 or 8, and — for each claimed castling right — that the relevant king and rook are actually still on their home squares. fen_serialize()walks the same board back into a FEN string (run-length encoding empty squares), which the CLI prints as a round-trip check.src/render.cmaps piece letters to Unicode chess glyphs (or plain ASCII) for the terminal board.tests/test_fen.cis a small assert-based suite: round-trip fidelity for real positions, and rejection of malformed rank lengths, missing kings, unbacked castling rights, and bad en passant squares.
MIT, see LICENSE.