Set up a per-game interpretation layer atop the SGF, and bind all games together in a Game
data structure #49
12
Makefile
12
Makefile
|
@ -1,4 +1,10 @@
|
|||
|
||||
all: test bin
|
||||
|
||||
test: kifu-core/test-oneshot sgf/test-oneshot
|
||||
|
||||
bin: kifu-gtk
|
||||
|
||||
changeset-dev:
|
||||
cd changeset && make dev
|
||||
|
||||
|
@ -58,3 +64,9 @@ kifu-pwa/dev:
|
|||
|
||||
kifu-pwa/server:
|
||||
pushd kifu/pwa && make server
|
||||
|
||||
sgf/test:
|
||||
pushd sgf && make test
|
||||
|
||||
sgf/test-oneshot:
|
||||
pushd sgf && make test-oneshot
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
mod date;
|
||||
pub use date::Date;
|
||||
|
||||
mod go;
|
||||
pub use go::{parse_sgf, GameTree, GameType, Rank};
|
||||
|
||||
mod tree;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
pub enum Warning {}
|
||||
|
||||
#[derive(Debug, PartialEq, Error)]
|
||||
pub enum ParseError {
|
||||
#[error("An unknown error was found")]
|
||||
NomError(nom::error::Error<String>),
|
||||
}
|
||||
|
||||
impl From<nom::error::Error<&str>> for ParseError {
|
||||
fn from(err: nom::error::Error<&str>) -> Self {
|
||||
Self::NomError(nom::error::Error {
|
||||
input: err.input.to_owned(),
|
||||
code: err.code.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
impl From<(&str, VerboseErrorKind)> for
|
||||
|
||||
impl From<nom::error::VerboseError<&str>> for ParseError {
|
||||
fn from(err: nom::error::VerboseError<&str>) -> Self {
|
||||
Self::NomErrors(
|
||||
err.errors
|
||||
.into_iter()
|
||||
.map(|err| ParseError::from(err))
|
||||
.collect(),
|
||||
)
|
||||
/*
|
||||
Self::NomError(nom::error::Error {
|
||||
input: err.input.to_owned(),
|
||||
code: err.code.clone(),
|
||||
})
|
||||
*/
|
||||
}
|
||||
}
|
||||
*/
|
|
@ -120,17 +120,6 @@ dependencies = [
|
|||
"syn 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "go-sgf"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"nom",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"typeshare",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "grid"
|
||||
version = "0.9.0"
|
||||
|
@ -191,10 +180,10 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"chrono",
|
||||
"cool_asserts",
|
||||
"go-sgf",
|
||||
"grid",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sgf",
|
||||
"thiserror",
|
||||
"typeshare",
|
||||
]
|
||||
|
@ -337,6 +326,17 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sgf"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"nom",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"typeshare",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
|
|
|
@ -7,7 +7,7 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4" }
|
||||
go-sgf = { path = "../../go-sgf" }
|
||||
sgf = { path = "../../sgf" }
|
||||
grid = { version = "0.9" }
|
||||
serde_json = { version = "1" }
|
||||
serde = { version = "1", features = [ "derive" ] }
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::{ffi::OsStr, io::Read, os::unix::ffi::OsStrExt, path::PathBuf};
|
||||
|
||||
use go_sgf::{parse_sgf, GameTree};
|
||||
use sgf::{go, parse_sgf, Game};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
|
@ -20,12 +20,12 @@ impl From<std::io::Error> for Error {
|
|||
#[derive(Debug)]
|
||||
pub struct Database {
|
||||
path: PathBuf,
|
||||
games: Vec<GameTree>,
|
||||
games: Vec<go::Game>,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
pub fn open_path(path: PathBuf) -> Result<Database, Error> {
|
||||
let mut games: Vec<GameTree> = Vec::new();
|
||||
let mut games: Vec<go::Game> = Vec::new();
|
||||
|
||||
let extension = PathBuf::from("sgf").into_os_string();
|
||||
|
||||
|
@ -39,8 +39,12 @@ impl Database {
|
|||
.unwrap()
|
||||
.read_to_string(&mut buffer)
|
||||
.unwrap();
|
||||
let sgf = parse_sgf(&buffer).unwrap();
|
||||
games.extend(sgf);
|
||||
for sgf in parse_sgf(&buffer).unwrap() {
|
||||
match sgf {
|
||||
Game::Go(game) => games.push(game),
|
||||
Game::Unsupported(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => println!("failed entry: {:?}", err),
|
||||
|
@ -54,7 +58,7 @@ impl Database {
|
|||
self.games.len()
|
||||
}
|
||||
|
||||
pub fn all_games(&self) -> impl Iterator<Item = &GameTree> {
|
||||
pub fn all_games(&self) -> impl Iterator<Item = &go::Game> {
|
||||
self.games.iter()
|
||||
}
|
||||
}
|
||||
|
@ -63,7 +67,7 @@ impl Database {
|
|||
mod test {
|
||||
use super::*;
|
||||
use cool_asserts::assert_matches;
|
||||
use go_sgf::{Date, GameType};
|
||||
use sgf::Date;
|
||||
|
||||
#[test]
|
||||
fn it_reads_empty_database() {
|
||||
|
@ -77,9 +81,7 @@ mod test {
|
|||
let db =
|
||||
Database::open_path(PathBuf::from("fixtures/five_games/")).expect("database to open");
|
||||
assert_eq!(db.all_games().count(), 5);
|
||||
for game in db.all_games() {
|
||||
assert_eq!(game.game_type, GameType::Go);
|
||||
}
|
||||
for game in db.all_games() {}
|
||||
|
||||
assert_matches!(db.all_games().find(|g| g.info.black_player == Some("Steve".to_owned())),
|
||||
Some(game) => {
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
use go_sgf::{Date, GameTree, Rank};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sgf::{
|
||||
go::{Game, Rank},
|
||||
Date,
|
||||
};
|
||||
use typeshare::typeshare;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
|
@ -13,7 +16,7 @@ pub struct GamePreviewElement {
|
|||
}
|
||||
|
||||
impl GamePreviewElement {
|
||||
pub fn new(game: &GameTree) -> GamePreviewElement {
|
||||
pub fn new(game: &Game) -> GamePreviewElement {
|
||||
GamePreviewElement {
|
||||
date: game.info.date.clone(),
|
||||
black_player: game
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::ui::{Action, GamePreviewElement};
|
||||
use go_sgf::GameTree;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sgf::go::Game;
|
||||
use typeshare::typeshare;
|
||||
|
||||
fn rank_strings() -> Vec<String> {
|
||||
|
@ -56,7 +56,7 @@ pub struct HomeView {
|
|||
pub start_game: Action<()>,
|
||||
}
|
||||
|
||||
pub fn home<'a>(games: impl Iterator<Item = &'a GameTree>) -> HomeView {
|
||||
pub fn home<'a>(games: impl Iterator<Item = &'a Game>) -> HomeView {
|
||||
let black_player = PlayerElement::Hotseat(HotseatPlayerElement {
|
||||
placeholder: Some("black player".to_owned()),
|
||||
default_rank: None,
|
||||
|
|
|
@ -545,17 +545,6 @@ dependencies = [
|
|||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "go-sgf"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"nom",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"typeshare",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gobject-sys"
|
||||
version = "0.17.4"
|
||||
|
@ -799,10 +788,10 @@ name = "kifu-core"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"go-sgf",
|
||||
"grid",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sgf",
|
||||
"thiserror",
|
||||
"typeshare",
|
||||
]
|
||||
|
@ -1237,6 +1226,17 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sgf"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"nom",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"typeshare",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.1"
|
||||
|
|
|
@ -63,17 +63,6 @@ version = "0.8.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
|
||||
|
||||
[[package]]
|
||||
name = "go-sgf"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"nom",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"typeshare",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.57"
|
||||
|
@ -216,6 +205,17 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sgf"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"nom",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"typeshare",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "go-sgf"
|
||||
name = "sgf"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
|
@ -69,33 +69,103 @@
|
|||
// VW
|
||||
|
||||
use crate::{
|
||||
date::{self, parse_date_field, Date},
|
||||
tree::{parse_collection, ParseSizeError, Size},
|
||||
date::{parse_date_field, Date},
|
||||
tree::{Size, Tree},
|
||||
Error,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error<'a> {
|
||||
InvalidField,
|
||||
InvalidBoardSize,
|
||||
Incomplete,
|
||||
InvalidSgf(nom::error::VerboseError<&'a str>),
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Game {
|
||||
pub board_size: Size,
|
||||
pub info: GameInfo,
|
||||
|
||||
pub tree: Tree,
|
||||
}
|
||||
|
||||
impl<'a> From<nom::Err<nom::error::VerboseError<&'a str>>> for Error<'a> {
|
||||
fn from(err: nom::Err<nom::error::VerboseError<&'a str>>) -> Self {
|
||||
match err {
|
||||
nom::Err::Incomplete(_) => Error::Incomplete,
|
||||
nom::Err::Error(e) => Error::InvalidSgf(e.clone()),
|
||||
nom::Err::Failure(e) => Error::InvalidSgf(e.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TryFrom<Tree> for Game {
|
||||
type Error = Error;
|
||||
|
||||
impl<'a> From<ParseSizeError> for Error<'a> {
|
||||
fn from(_: ParseSizeError) -> Self {
|
||||
Self::InvalidBoardSize
|
||||
fn try_from(tree: Tree) -> Result<Self, Self::Error> {
|
||||
let board_size = match tree.sequence[0].find_prop("SZ") {
|
||||
Some(prop) => Size::try_from(prop.values[0].as_str())?,
|
||||
None => Size {
|
||||
width: 19,
|
||||
height: 19,
|
||||
},
|
||||
};
|
||||
let mut info = GameInfo::default();
|
||||
info.app_name = tree.sequence[0]
|
||||
.find_prop("AP")
|
||||
.map(|prop| prop.values[0].clone());
|
||||
info.black_player = tree.sequence[0]
|
||||
.find_prop("PB")
|
||||
.map(|prop| prop.values.join(", "));
|
||||
|
||||
info.black_rank = tree.sequence[0]
|
||||
.find_prop("BR")
|
||||
.and_then(|prop| Rank::try_from(prop.values[0].as_str()).ok());
|
||||
|
||||
info.white_player = tree.sequence[0]
|
||||
.find_prop("PW")
|
||||
.map(|prop| prop.values.join(", "));
|
||||
|
||||
info.white_rank = tree.sequence[0]
|
||||
.find_prop("WR")
|
||||
.and_then(|prop| Rank::try_from(prop.values[0].as_str()).ok());
|
||||
|
||||
info.result = tree.sequence[0]
|
||||
.find_prop("RE")
|
||||
.and_then(|prop| GameResult::try_from(prop.values[0].as_str()).ok());
|
||||
|
||||
info.time_limits = tree.sequence[0]
|
||||
.find_prop("TM")
|
||||
.and_then(|prop| prop.values[0].parse::<u64>().ok())
|
||||
.and_then(|seconds| Some(std::time::Duration::from_secs(seconds)));
|
||||
|
||||
info.date = tree.sequence[0]
|
||||
.find_prop("DT")
|
||||
.and_then(|prop| {
|
||||
let v = prop
|
||||
.values
|
||||
.iter()
|
||||
.map(|val| parse_date_field(val))
|
||||
.fold(Ok(vec![]), |acc, v| match (acc, v) {
|
||||
(Ok(mut acc), Ok(mut values)) => {
|
||||
acc.append(&mut values);
|
||||
Ok(acc)
|
||||
}
|
||||
(Ok(_), Err(err)) => Err(err),
|
||||
(Err(err), _) => Err(err),
|
||||
})
|
||||
.ok()?;
|
||||
Some(v)
|
||||
})
|
||||
.unwrap_or(vec![]);
|
||||
|
||||
info.event = tree.sequence[0]
|
||||
.find_prop("EV")
|
||||
.map(|prop| prop.values.join(", "));
|
||||
|
||||
info.round = tree.sequence[0]
|
||||
.find_prop("RO")
|
||||
.map(|prop| prop.values.join(", "));
|
||||
|
||||
info.source = tree.sequence[0]
|
||||
.find_prop("SO")
|
||||
.map(|prop| prop.values.join(", "));
|
||||
|
||||
info.game_keeper = tree.sequence[0]
|
||||
.find_prop("US")
|
||||
.map(|prop| prop.values.join(", "));
|
||||
|
||||
Ok(Game {
|
||||
board_size,
|
||||
info,
|
||||
|
||||
tree,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,18 +197,9 @@ impl ToString for Rank {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GameTree {
|
||||
pub file_format: i8,
|
||||
pub app_name: Option<String>,
|
||||
pub game_type: GameType,
|
||||
pub board_size: Size,
|
||||
pub info: GameInfo,
|
||||
// pub text: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct GameInfo {
|
||||
pub app_name: Option<String>,
|
||||
pub annotator: Option<String>,
|
||||
pub copyright: Option<String>,
|
||||
pub event: Option<String>,
|
||||
|
@ -213,12 +274,6 @@ pub enum Win {
|
|||
Time,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum GameType {
|
||||
Go,
|
||||
Unsupported,
|
||||
}
|
||||
|
||||
/*
|
||||
enum PropType {
|
||||
Move,
|
||||
|
@ -241,104 +296,13 @@ enum PropValue {
|
|||
}
|
||||
*/
|
||||
|
||||
pub fn parse_sgf<'a>(input: &'a str) -> Result<Vec<GameTree>, Error<'a>> {
|
||||
let (_, trees) = parse_collection::<nom::error::VerboseError<&'a str>>(input)?;
|
||||
|
||||
let games = trees
|
||||
.into_iter()
|
||||
.map(|tree| {
|
||||
let file_format = match tree.sequence[0].find_prop("FF") {
|
||||
Some(prop) => prop.values[0].parse::<i8>().unwrap(),
|
||||
None => 4,
|
||||
};
|
||||
let app_name = tree.sequence[0]
|
||||
.find_prop("AP")
|
||||
.map(|prop| prop.values[0].clone());
|
||||
let board_size = match tree.sequence[0].find_prop("SZ") {
|
||||
Some(prop) => Size::try_from(prop.values[0].as_str())?,
|
||||
None => Size {
|
||||
width: 19,
|
||||
height: 19,
|
||||
},
|
||||
};
|
||||
let mut info = GameInfo::default();
|
||||
info.black_player = tree.sequence[0]
|
||||
.find_prop("PB")
|
||||
.map(|prop| prop.values.join(", "));
|
||||
|
||||
info.black_rank = tree.sequence[0]
|
||||
.find_prop("BR")
|
||||
.and_then(|prop| Rank::try_from(prop.values[0].as_str()).ok());
|
||||
|
||||
info.white_player = tree.sequence[0]
|
||||
.find_prop("PW")
|
||||
.map(|prop| prop.values.join(", "));
|
||||
|
||||
info.white_rank = tree.sequence[0]
|
||||
.find_prop("WR")
|
||||
.and_then(|prop| Rank::try_from(prop.values[0].as_str()).ok());
|
||||
|
||||
info.result = tree.sequence[0]
|
||||
.find_prop("RE")
|
||||
.and_then(|prop| GameResult::try_from(prop.values[0].as_str()).ok());
|
||||
|
||||
info.time_limits = tree.sequence[0]
|
||||
.find_prop("TM")
|
||||
.and_then(|prop| prop.values[0].parse::<u64>().ok())
|
||||
.and_then(|seconds| Some(std::time::Duration::from_secs(seconds)));
|
||||
|
||||
info.date = tree.sequence[0]
|
||||
.find_prop("DT")
|
||||
.and_then(|prop| {
|
||||
let v = prop
|
||||
.values
|
||||
.iter()
|
||||
.map(|val| parse_date_field(val))
|
||||
.fold(Ok(vec![]), |acc, v| match (acc, v) {
|
||||
(Ok(mut acc), Ok(mut values)) => {
|
||||
acc.append(&mut values);
|
||||
Ok(acc)
|
||||
}
|
||||
(Ok(_), Err(err)) => Err(err),
|
||||
(Err(err), _) => Err(err),
|
||||
})
|
||||
.ok()?;
|
||||
Some(v)
|
||||
})
|
||||
.unwrap_or(vec![]);
|
||||
|
||||
info.event = tree.sequence[0]
|
||||
.find_prop("EV")
|
||||
.map(|prop| prop.values.join(", "));
|
||||
|
||||
info.round = tree.sequence[0]
|
||||
.find_prop("RO")
|
||||
.map(|prop| prop.values.join(", "));
|
||||
|
||||
info.source = tree.sequence[0]
|
||||
.find_prop("SO")
|
||||
.map(|prop| prop.values.join(", "));
|
||||
|
||||
info.game_keeper = tree.sequence[0]
|
||||
.find_prop("US")
|
||||
.map(|prop| prop.values.join(", "));
|
||||
|
||||
Ok(GameTree {
|
||||
file_format,
|
||||
app_name,
|
||||
game_type: GameType::Go,
|
||||
board_size,
|
||||
info,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<GameTree>, Error>>()?;
|
||||
Ok(games)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{date::Date, tree::Size};
|
||||
use crate::{
|
||||
date::Date,
|
||||
tree::{parse_collection, Size},
|
||||
};
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
|
@ -347,12 +311,16 @@ mod tests {
|
|||
(;C[f](;C[g];C[h];C[i])
|
||||
(;C[j])))";
|
||||
|
||||
fn with_text(text: &str, f: impl FnOnce(Vec<GameTree>)) {
|
||||
let games = parse_sgf(text).unwrap();
|
||||
fn with_text(text: &str, f: impl FnOnce(Vec<Game>)) {
|
||||
let (_, games) = parse_collection::<nom::error::VerboseError<&str>>(text).unwrap();
|
||||
let games = games
|
||||
.into_iter()
|
||||
.map(|game| Game::try_from(game).expect("game to parse"))
|
||||
.collect::<Vec<Game>>();
|
||||
f(games);
|
||||
}
|
||||
|
||||
fn with_file(path: &std::path::Path, f: impl FnOnce(Vec<GameTree>)) {
|
||||
fn with_file(path: &std::path::Path, f: impl FnOnce(Vec<Game>)) {
|
||||
let mut file = File::open(path).unwrap();
|
||||
let mut text = String::new();
|
||||
let _ = file.read_to_string(&mut text);
|
||||
|
@ -364,9 +332,7 @@ mod tests {
|
|||
with_text(EXAMPLE, |trees| {
|
||||
assert_eq!(trees.len(), 1);
|
||||
let tree = &trees[0];
|
||||
assert_eq!(tree.file_format, 4);
|
||||
assert_eq!(tree.app_name, None);
|
||||
assert_eq!(tree.game_type, GameType::Go);
|
||||
assert_eq!(tree.info.app_name, None);
|
||||
assert_eq!(
|
||||
tree.board_size,
|
||||
Size {
|
||||
|
@ -380,9 +346,7 @@ mod tests {
|
|||
with_file(std::path::Path::new("test_data/print1.sgf"), |trees| {
|
||||
assert_eq!(trees.len(), 1);
|
||||
let tree = &trees[0];
|
||||
assert_eq!(tree.file_format, 4);
|
||||
assert_eq!(tree.app_name, None);
|
||||
assert_eq!(tree.game_type, GameType::Go);
|
||||
assert_eq!(tree.info.app_name, None);
|
||||
assert_eq!(
|
||||
tree.board_size,
|
||||
Size {
|
|
@ -0,0 +1,96 @@
|
|||
mod date;
|
||||
pub use date::Date;
|
||||
|
||||
pub mod go;
|
||||
|
||||
mod tree;
|
||||
use tree::parse_collection;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
InvalidField,
|
||||
InvalidBoardSize,
|
||||
Incomplete,
|
||||
InvalidSgf(VerboseNomError),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VerboseNomError(nom::error::VerboseError<String>);
|
||||
|
||||
impl From<nom::error::VerboseError<&str>> for VerboseNomError {
|
||||
fn from(err: nom::error::VerboseError<&str>) -> Self {
|
||||
VerboseNomError(nom::error::VerboseError {
|
||||
errors: err
|
||||
.errors
|
||||
.into_iter()
|
||||
.map(|err| (err.0.to_owned(), err.1))
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<nom::Err<nom::error::VerboseError<&str>>> for Error {
|
||||
fn from(err: nom::Err<nom::error::VerboseError<&str>>) -> Self {
|
||||
match err {
|
||||
nom::Err::Incomplete(_) => Error::Incomplete,
|
||||
nom::Err::Error(e) => Error::InvalidSgf(VerboseNomError::from(e)),
|
||||
nom::Err::Failure(e) => Error::InvalidSgf(VerboseNomError::from(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Error)]
|
||||
pub enum ParseError {
|
||||
#[error("An unknown error was found")]
|
||||
NomError(nom::error::Error<String>),
|
||||
}
|
||||
|
||||
impl From<nom::error::Error<&str>> for ParseError {
|
||||
fn from(err: nom::error::Error<&str>) -> Self {
|
||||
Self::NomError(nom::error::Error {
|
||||
input: err.input.to_owned(),
|
||||
code: err.code.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Game {
|
||||
Go(go::Game),
|
||||
Unsupported(tree::Tree),
|
||||
}
|
||||
|
||||
pub fn parse_sgf(input: &str) -> Result<Vec<Game>, Error> {
|
||||
let (_, trees) = parse_collection::<nom::error::VerboseError<&str>>(input)?;
|
||||
Ok(trees
|
||||
.into_iter()
|
||||
.map(|t| match t.sequence[0].find_prop("GM") {
|
||||
Some(prop) if prop.values == vec!["1".to_owned()] => {
|
||||
Game::Go(go::Game::try_from(t).expect("properly structured game tree"))
|
||||
}
|
||||
_ => Game::Unsupported(t),
|
||||
})
|
||||
.collect::<Vec<Game>>())
|
||||
}
|
||||
|
||||
/*
|
||||
impl From<(&str, VerboseErrorKind)> for
|
||||
|
||||
impl From<nom::error::VerboseError<&str>> for ParseError {
|
||||
fn from(err: nom::error::VerboseError<&str>) -> Self {
|
||||
Self::NomErrors(
|
||||
err.errors
|
||||
.into_iter()
|
||||
.map(|err| ParseError::from(err))
|
||||
.collect(),
|
||||
)
|
||||
/*
|
||||
Self::NomError(nom::error::Error {
|
||||
input: err.input.to_owned(),
|
||||
code: err.code.clone(),
|
||||
})
|
||||
*/
|
||||
}
|
||||
}
|
||||
*/
|
|
@ -1,3 +1,4 @@
|
|||
use crate::Error;
|
||||
use nom::{
|
||||
branch::alt,
|
||||
bytes::complete::{escaped_transform, tag},
|
||||
|
@ -9,6 +10,12 @@ use nom::{
|
|||
};
|
||||
use std::num::ParseIntError;
|
||||
|
||||
impl From<ParseSizeError> for Error {
|
||||
fn from(_: ParseSizeError) -> Self {
|
||||
Self::InvalidBoardSize
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ParseSizeError {
|
||||
ParseIntError(ParseIntError),
|
||||
|
@ -45,7 +52,7 @@ impl TryFrom<&str> for Size {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Tree {
|
||||
pub sequence: Vec<Node>,
|
||||
pub sub_sequences: Vec<Tree>,
|
||||
|
@ -67,7 +74,7 @@ impl ToString for Tree {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Node {
|
||||
pub properties: Vec<Property>,
|
||||
}
|
Loading…
Reference in New Issue