use pest::{
Parser,
iterators::{Pair, Pairs},
};
use crate::{Game, GameResult, Position, San, ruleset::classic::CLASSIC_RULESET};
use super::{Pgn, error::PgnParserError};
#[derive(Parser)]
#[grammar = "parser/portable_game_notation/portable_game_notation.pest"]
struct PgnStruct;
impl Pgn {
pub fn import(pgn_string: &str) -> Result<Game, PgnParserError> {
let pgn_lines: Vec<&str> = pgn_string.lines().collect();
let mut game = Game::new_from_position(&CLASSIC_RULESET, Position::default());
let part_seperator = match Self::find_empty_line(&pgn_lines) {
Some(index) => index,
None => pgn_lines.len(),
};
Self::parse_metadata_lines(&pgn_lines[0..part_seperator], &mut game)?;
let mut turn_data = String::new();
for line in &pgn_lines[part_seperator + 1..] {
turn_data.push_str(line);
turn_data.push(' ');
}
let Ok(pairs) = PgnStruct::parse(Rule::turn_data, &turn_data) else {
return Err(PgnParserError::InvalidTurnData(turn_data));
};
let turn_data_pair = pairs.collect::<Vec<Pair<Rule>>>()[0].clone();
for pair in turn_data_pair.into_inner() {
if matches!(
pair.as_rule(),
Rule::single_move_entry | Rule::two_move_entry
) {
Self::handle_move_entry(pair.into_inner(), &mut game)?
} else if pair.as_rule() == Rule::game_result {
let result = pair.as_str();
game.set_game_result(GameResult::from_string(result));
}
}
Ok(game)
}
fn handle_move_entry(pairs: Pairs<Rule>, game: &mut Game) -> Result<(), PgnParserError> {
for pair in pairs {
if pair.as_rule() == Rule::turn_move {
let turn_rule = pair
.into_inner()
.find(|pair| pair.as_rule() == Rule::san_move)
.unwrap();
let Ok(turn) = San::import(turn_rule.as_str(), game.get_current_state()) else {
return Err(PgnParserError::IllegalTurn(turn_rule.as_str().to_string()));
};
if game.execute_turn(turn).is_err() {
return Err(PgnParserError::IllegalTurn(turn_rule.as_str().to_string()));
}
}
}
Ok(())
}
fn find_empty_line(lines: &[&str]) -> Option<usize> {
for (index, line) in lines.iter().enumerate() {
if line.is_empty() {
return Some(index);
}
}
None
}
fn parse_metadata_lines(lines: &[&str], game: &mut Game) -> Result<(), PgnParserError> {
for line in lines {
let mut key = String::new();
match PgnStruct::parse(Rule::metadata, line) {
Ok(pairs) => {
for rules in pairs.collect::<Vec<Pair<Rule>>>()[0].clone().into_inner() {
match rules.as_rule() {
Rule::metadata_key => key = rules.as_str().to_string(),
Rule::metadata_value => game.set_metadata(&key, rules.as_str()),
_ => {
return Err(PgnParserError::InvalidMetadataContent(
line.to_string(),
));
}
}
}
}
Err(_) => return Err(PgnParserError::InvalidMetadataContent(line.to_string())),
}
}
Ok(())
}
}