libchess/pkg/pgn/move/parser.go
2020-05-08 22:55:36 +02:00

225 lines
3.8 KiB
Go

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
}