package move import ( "fmt" "code.c-base.org/gochess/libchess/pkg/board" ) // Parser implements a parser for PGN moves. type Parser struct { lexer *Lexer } // NewParser returns an initialized parser for the given move. func NewParser(m string) *Parser { return &Parser{ lexer: NewLexer(m), } } // Move parses the move and returns it or an error. func (p *Parser) Move() (*board.Move, error) { var ( stateCastles bool statePiece bool stateDisambiguity bool stateCaptures bool stateSquare bool stateCheck bool move = &board.Move{} ) parsing: for { t := p.lexer.NextToken() if t.Type == TokenEOF { if move.To == board.NoSquare { if !move.HasProp(board.KingSideCastle) && !move.HasProp(board.QueenSideCastle) { return nil, p.throwToken(t) } } return move, nil } if !stateCastles { stateCastles = true if parseCastles(t, move) { continue parsing } } if !statePiece { statePiece = true if parsePiece(t, move) { continue parsing } } if !stateDisambiguity { stateDisambiguity = true if parseDisambiguity(t, move) { continue parsing } } if !stateCaptures { stateCaptures = true if parseCaptures(t, move) { continue parsing } } if !stateSquare { stateSquare = true if parseSquare(t, move) { continue parsing } } if !stateCheck { stateCheck = true if parseCheckMate(t, move) { continue parsing } } } } func (p Parser) throwToken(t Token) error { return fmt.Errorf("invalid token at pos %d: %s", t.Pos, t.Value) } /////////////////////// //// PARSE CASTLES //// /////////////////////// func parseCastles(t Token, m *board.Move) bool { if t.Type == TokenCastles { switch t.Value { case "O-O", "0-0": m.AddProp(board.KingSideCastle) return true case "O-O-O", "0-0-0": m.AddProp(board.QueenSideCastle) return true } } return false } ///////////////////// //// PARSE PIECE //// ///////////////////// var legalPieces = map[string]board.PieceType{ "K": board.King, "Q": board.Queen, "B": board.Bishop, "N": board.Knight, "R": board.Rook, } func parsePiece(t Token, m *board.Move) bool { if t.Type != TokenPiece { return false } p, ok := legalPieces[t.Value] if ok { m.Piece = p return true } return false } /////////////////////// //// PARSE SQUARES //// /////////////////////// var ( legalFiles = map[string]board.File{ "a": board.FileA, "b": board.FileB, "c": board.FileC, "d": board.FileD, "e": board.FileE, "f": board.FileF, "g": board.FileG, "h": board.FileH, } legalRanks = map[string]board.Rank{ "1": board.Rank1, "2": board.Rank2, "3": board.Rank3, "4": board.Rank4, "5": board.Rank5, "6": board.Rank6, "7": board.Rank7, "8": board.Rank8, } ) func parseDisambiguity(t Token, m *board.Move) bool { if t.Type == TokenFile { f, ok := legalFiles[t.Value] if ok { m.FromFile = &f return true } } if t.Type == TokenRank { r, ok := legalRanks[t.Value] if ok { m.FromRank = &r return true } } return false } func parseSquare(t Token, m *board.Move) bool { if t.Type == TokenSquare { m.To = board.StrToSquareMap[t.Value] return true } return false } /////////////////////// //// PARSE CAPTURE //// /////////////////////// var legalCapture = map[string]struct{}{ "x": {}, } func parseCaptures(t Token, m *board.Move) bool { if t.Type == TokenCapture { _, ok := legalCapture[t.Value] if ok { m.AddProp(board.Capture) return true } } return false } ////////////////////////// //// PARSE CHECK/MATE //// ////////////////////////// func parseCheckMate(t Token, m *board.Move) bool { if t.Type == TokenCheck { if t.Value == "+" { m.AddProp(board.Check) return true } } if t.Type == TokenMate { if t.Value == "#" { m.AddProp(board.Mate) return true } } return false }