Compare commits

..

No commits in common. "52f663264e6b2ae439b480a3ccebe12de1ca248a" and "31e70bfc2a92939b2cb4005c194a7db923e342d6" have entirely different histories.

3 changed files with 264 additions and 253 deletions

View File

@ -1,4 +1,4 @@
use crate::{parser, Color}; use crate::{tree, Color, Position};
use std::{collections::HashSet, time::Duration}; use std::{collections::HashSet, time::Duration};
use uuid::Uuid; use uuid::Uuid;
@ -227,9 +227,9 @@ impl Node for GameNode {
} }
} }
impl TryFrom<&parser::Node> for GameNode { impl TryFrom<&tree::Node> for GameNode {
type Error = ConversionError; type Error = ConversionError;
fn try_from(n: &parser::Node) -> Result<Self, Self::Error> { fn try_from(n: &tree::Node) -> Result<Self, Self::Error> {
unimplemented!() unimplemented!()
} }
} }
@ -260,7 +260,7 @@ impl Node for GameTree {
pub struct MoveNode { pub struct MoveNode {
id: Uuid, id: Uuid,
color: Color, color: Color,
position: String, position: Position,
children: Vec<GameNode>, children: Vec<GameNode>,
time_left: Option<Duration>, time_left: Option<Duration>,
@ -274,7 +274,7 @@ pub struct MoveNode {
} }
impl MoveNode { impl MoveNode {
pub fn new(color: Color, position: String) -> Self { pub fn new(color: Color, position: Position) -> Self {
Self { Self {
id: Uuid::new_v4(), id: Uuid::new_v4(),
color, color,
@ -304,41 +304,42 @@ impl Node for MoveNode {
} }
} }
impl TryFrom<&parser::Node> for MoveNode { impl TryFrom<&tree::Node> for MoveNode {
type Error = ConversionError; type Error = ConversionError;
fn try_from(n: &parser::Node) -> Result<Self, Self::Error> { fn try_from(n: &tree::Node) -> Result<Self, Self::Error> {
match n.move_() { let move_ = match (n.find_prop("W"), n.find_prop("B")) {
Some((color, position)) => Ok(MoveNode::new(color, position)), (Some(white_move), _) => Some((Color::White, Position{ row: white_move
(None, Some(black_move)) => unimplemented!(),
(None, None) => None,
};
match move_ {
Some((color, position)) => unimplemented!(),
None => Err(ConversionError::IncompatibleNodeType), None => Err(ConversionError::IncompatibleNodeType),
} }
} }
} }
/*
fn parse_position(s: &str) -> Result<Position, ConversionError> { fn parse_position(s: &str) -> Result<Position, ConversionError> {
if s.len() == 2 { if s.len() == 2 {
Ok(Position { Ok(Position{ row: s[0], column: s[1] })
row: s[0],
column: s[1],
})
} else { } else {
Err(ConversionError::InvalidPositionSyntax) Err(ConversionError::InvalidPositionSyntax)
} }
} }
*/
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct SetupNode { pub struct SetupNode {
id: Uuid, id: Uuid,
positions: Vec<(Option<Color>, String)>, positions: Vec<(Option<Color>, Position)>,
children: Vec<GameNode>, children: Vec<GameNode>,
} }
impl SetupNode { impl SetupNode {
pub fn new(positions: Vec<(Option<Color>, String)>) -> Result<Self, SetupError> { pub fn new(positions: Vec<(Option<Color>, Position)>) -> Result<Self, SetupError> {
let mut coords: HashSet<String> = HashSet::new(); let mut coords: HashSet<Position> = HashSet::new();
for coord in positions.iter().map(|p| p.1.clone()) { for coord in positions.iter().map(|p| p.1.clone()) {
if coords.contains(&coord) { if coords.contains(&coord) {
return Err(SetupError::ConflictingPosition); return Err(SetupError::ConflictingPosition);
@ -395,9 +396,21 @@ mod test {
fn it_can_add_moves_to_a_game() { fn it_can_add_moves_to_a_game() {
let mut tree = GameTree::new(); let mut tree = GameTree::new();
let first_move = MoveNode::new(Color::Black, "dd".to_owned()); let first_move = MoveNode::new(
Color::Black,
Position {
row: 'd',
column: 'd',
},
);
let first_ = tree.add_child(GameNode::MoveNode(first_move.clone())); let first_ = tree.add_child(GameNode::MoveNode(first_move.clone()));
let second_move = MoveNode::new(Color::White, "qq".to_owned()); let second_move = MoveNode::new(
Color::White,
Position {
row: 'q',
column: 'q',
},
);
first_.add_child(GameNode::MoveNode(second_move.clone())); first_.add_child(GameNode::MoveNode(second_move.clone()));
let nodes = tree.nodes(); let nodes = tree.nodes();
@ -416,19 +429,20 @@ mod test {
#[test] #[test]
fn game_node_can_parse_sgf_move_node() { fn game_node_can_parse_sgf_move_node() {
let n = parser::Node { let n = tree::Node {
properties: vec![ properties: vec![
parser::Property::Move((Color::White, "dp".to_owned())), tree::Property {
/* ident: "W".to_owned(),
parser::Property { values: vec!["dp".to_owned()],
},
tree::Property {
ident: "WL".to_owned(), ident: "WL".to_owned(),
values: vec!["176.099".to_owned()], values: vec!["176.099".to_owned()],
}, },
parser::Property { tree::Property {
ident: "C".to_owned(), ident: "C".to_owned(),
values: vec!["Comments in the game".to_owned()], values: vec!["Comments in the game".to_owned()],
}, },
*/
], ],
next: vec![], next: vec![],
}; };
@ -458,25 +472,26 @@ mod move_node_tests {
#[test] #[test]
fn it_can_parse_an_sgf_move_node() { fn it_can_parse_an_sgf_move_node() {
let n = parser::Node { let n = tree::Node {
properties: vec![ properties: vec![
parser::Property::Move((Color::White, "dp".to_owned())), tree::Property {
/* ident: "W".to_owned(),
parser::Property { values: vec!["dp".to_owned()],
},
tree::Property {
ident: "WL".to_owned(), ident: "WL".to_owned(),
values: vec!["176.099".to_owned()], values: vec!["176.099".to_owned()],
}, },
parser::Property { tree::Property {
ident: "C".to_owned(), ident: "C".to_owned(),
values: vec!["Comments in the game".to_owned()], values: vec!["Comments in the game".to_owned()],
}, },
*/
], ],
next: vec![], next: vec![],
}; };
assert_matches!(MoveNode::try_from(&n), Ok(node) => { assert_matches!(MoveNode::try_from(&n), Ok(node) => {
assert_eq!(node.color, Color::White); assert_eq!(node.color, Color::White);
assert_eq!(node.position, "dp".to_owned()); assert_eq!(node.position, Position{ row: 'd', column: 'p' });
assert_eq!(node.children, vec![]); assert_eq!(node.children, vec![]);
assert_eq!(node.time_left, Some(Duration::from_secs(176))); assert_eq!(node.time_left, Some(Duration::from_secs(176)));
assert_eq!(node.comments, vec!["Comments in the game".to_owned()]); assert_eq!(node.comments, vec!["Comments in the game".to_owned()]);
@ -502,16 +517,46 @@ mod setup_node_tests {
fn it_rejects_conflicting_placement_properties() { fn it_rejects_conflicting_placement_properties() {
assert_matches!( assert_matches!(
SetupNode::new(vec![ SetupNode::new(vec![
(Some(Color::Black), "dd".to_owned(),), (
(Some(Color::Black), "dd".to_owned(),), Some(Color::Black),
Position {
row: 'd',
column: 'd',
},
),
(
Some(Color::Black),
Position {
row: 'd',
column: 'd',
},
),
]), ]),
Err(SetupError::ConflictingPosition) Err(SetupError::ConflictingPosition)
); );
assert_matches!( assert_matches!(
SetupNode::new(vec![ SetupNode::new(vec![
(Some(Color::Black), "dd".to_owned(),), (
(Some(Color::Black), "ee".to_owned(),), Some(Color::Black),
(Some(Color::White), "ee".to_owned(),), Position {
row: 'd',
column: 'd',
},
),
(
Some(Color::Black),
Position {
row: 'e',
column: 'e',
},
),
(
Some(Color::White),
Position {
row: 'e',
column: 'e',
},
),
]), ]),
Err(SetupError::ConflictingPosition) Err(SetupError::ConflictingPosition)
); );

View File

@ -3,10 +3,10 @@ pub use date::Date;
// pub mod go; // pub mod go;
mod game; mod tree;
use tree::{parse_collection, Tree};
mod parser; // mod game;
use parser::{parse_collection, Tree};
use thiserror::Error; use thiserror::Error;

View File

@ -1,12 +1,11 @@
use crate::{Color, Error, GameResult}; use crate::{Color, Error, GameResult};
use nom::{ use nom::{
branch::alt, branch::alt,
bytes::complete::{escaped_transform, tag, take_until1}, bytes::complete::{escaped_transform, tag},
character::complete::{alpha1, digit1, multispace0, multispace1, none_of}, character::complete::{alpha1, multispace0, multispace1, none_of},
combinator::{opt, value}, combinator::{opt, value},
error::ParseError,
multi::{many0, many1, separated_list1}, multi::{many0, many1, separated_list1},
IResult, Parser, IResult,
}; };
use std::num::ParseIntError; use std::num::ParseIntError;
@ -28,12 +27,6 @@ impl From<ParseIntError> for ParseSizeError {
} }
} }
#[derive(Clone, Debug, PartialEq)]
pub enum Double {
Normal,
Emphasized,
}
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum GameType { pub enum GameType {
Go, Go,
@ -238,18 +231,6 @@ pub struct Node {
pub next: Vec<Node>, pub next: Vec<Node>,
} }
impl Node {
pub fn move_(&self) -> Option<(Color, String)> {
self.properties
.iter()
.filter_map(|prop| match prop {
Property::Move(val) => Some(val.clone()),
_ => None,
})
.next()
}
}
impl ToString for Node { impl ToString for Node {
fn to_string(&self) -> String { fn to_string(&self) -> String {
let props = self let props = self
@ -275,6 +256,21 @@ impl ToString for Node {
} }
} }
/*
impl Node {
pub fn find_prop(&self, ident: &str) -> Option<Property> {
self.properties
.iter()
.find(|prop| prop.ident == ident)
.cloned()
}
pub fn next<'a>(&'a self) -> Option<&'a Node> {
self.next.get(0)
}
}
*/
// KO // KO
// MN // MN
// N // N
@ -297,10 +293,10 @@ impl ToString for Node {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum Property { pub enum Property {
// B, W // B, W
Move((Color, String)), Move((Color, Position)),
// C // C
Comment(String), Comment(Vec<String>),
// BM // BM
BadMove, BadMove,
@ -321,7 +317,7 @@ pub enum Property {
Charset(String), Charset(String),
// FF // FF
FileFormat(i32), FileFormat(u8),
// GM // GM
GameType(GameType), GameType(GameType),
@ -431,16 +427,30 @@ pub enum Property {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct UnknownProperty { pub struct UnknownProperty {
ident: String, ident: String,
value: String, values: Vec<String>,
} }
/*
#[derive(Clone, Debug, PartialEq)]
pub struct Property {
pub ident: String,
pub values: Vec<String>,
}
*/
impl ToString for Property { impl ToString for Property {
fn to_string(&self) -> String { fn to_string(&self) -> String {
match self { match self {
Property::Move((color, position)) => { Property::Move((color, position)) => {
format!("{}[{}]", color.abbreviation(), position) format!("{}[{}]", color.abbreviation(), position.0)
} }
Property::Comment(value) => format!("C[{}]", value), Property::Comment(values) => format!(
"C{}",
values
.iter()
.map(|v| format!("[{}]", v))
.collect::<String>()
),
Property::BadMove => "BM[]".to_owned(), Property::BadMove => "BM[]".to_owned(),
Property::DoubtfulMove => "DO[]".to_owned(), Property::DoubtfulMove => "DO[]".to_owned(),
Property::InterestingMove => "IT[]".to_owned(), Property::InterestingMove => "IT[]".to_owned(),
@ -494,10 +504,18 @@ impl ToString for Property {
Property::User(value) => format!("US[{}]", value), Property::User(value) => format!("US[{}]", value),
Property::WhiteRank(value) => format!("WR[{}]", value), Property::WhiteRank(value) => format!("WR[{}]", value),
Property::WhiteTeam(value) => format!("WT[{}]", value), Property::WhiteTeam(value) => format!("WT[{}]", value),
Property::Unknown(UnknownProperty { ident, value }) => { Property::Unknown(UnknownProperty { ident, values }) => {
format!("{}[{}]", ident, value) let values = values
.iter()
.map(|val| format!("[{}]", val))
.collect::<String>();
format!("{}{}", ident, values)
} }
} }
/*
let values = self
.collect::<String>();
*/
} }
} }
@ -545,133 +563,77 @@ fn parse_property<'a, E: nom::error::ParseError<&'a str>>(
) -> IResult<&'a str, Property, E> { ) -> IResult<&'a str, Property, E> {
let (input, _) = multispace0(input)?; let (input, _) = multispace0(input)?;
let (input, ident) = alpha1(input)?; let (input, ident) = alpha1(input)?;
let (input, values) = many1(parse_propval)(input)?;
let (input, _) = multispace0(input)?;
let (input, prop) = match ident { let values = values
"W" => parse_propval(parse_move(Color::White))(input)?, .into_iter()
"B" => parse_propval(parse_move(Color::Black))(input)?, .map(|v| v.to_owned())
"C" => parse_propval(parse_comment())(input)?, .collect::<Vec<String>>();
"BM" => discard_propval().map(|_| Property::BadMove).parse(input)?,
"DO" => discard_propval() let prop = match ident {
.map(|_| Property::DoubtfulMove) "W" => Property::Move((Color::White, Position(values[0].clone()))),
.parse(input)?, "B" => Property::Move((Color::Black, Position(values[0].clone()))),
"IT" => discard_propval() "C" => Property::Comment(values),
.map(|_| Property::InterestingMove) "BM" => Property::BadMove,
.parse(input)?, "DO" => Property::DoubtfulMove,
"TE" => discard_propval().map(|_| Property::Tesuji).parse(input)?, "IT" => Property::InterestingMove,
"AP" => parse_propval(parse_simple_text().map(Property::Application))(input)?, "TE" => Property::Tesuji,
"CA" => parse_propval(parse_simple_text().map(Property::Charset))(input)?, "AP" => Property::Application(values.join(",")),
"FF" => parse_propval(parse_number().map(Property::FileFormat))(input)?, "CA" => Property::Charset(values.join(",")),
"GM" => unimplemented!(), "FF" => Property::FileFormat(values.join("").parse::<u8>().unwrap()),
"GM" => Property::GameType(GameType::from(values.join("").as_ref())),
"ST" => unimplemented!(), "ST" => unimplemented!(),
"SZ" => unimplemented!(), "SZ" => Property::BoardSize(Size::try_from(values.join("").as_ref()).unwrap()),
"DM" => discard_propval() "DM" => Property::EvenResult,
.map(|_| Property::EvenResult) "GB" => Property::GoodForBlack,
.parse(input)?, "GW" => Property::GoodForWhite,
"GB" => discard_propval() "UC" => Property::UnclearResult,
.map(|_| Property::GoodForBlack) "V" => Property::Value(values.join("").parse::<f32>().unwrap()),
.parse(input)?, "AN" => Property::Annotator(values.join("")),
"GW" => discard_propval() "BR" => Property::BlackRank(values.join("")),
.map(|_| Property::GoodForWhite) "BT" => Property::BlackTeam(values.join("")),
.parse(input)?, "CP" => Property::Copyright(values.join("")),
"UC" => discard_propval()
.map(|_| Property::UnclearResult)
.parse(input)?,
"V" => unimplemented!(),
"AN" => parse_propval(parse_simple_text().map(Property::Annotator))(input)?,
"BR" => parse_propval(parse_simple_text().map(Property::BlackRank))(input)?,
"BT" => parse_propval(parse_simple_text().map(Property::BlackTeam))(input)?,
"CP" => parse_propval(parse_simple_text().map(Property::Copyright))(input)?,
"DT" => unimplemented!(), "DT" => unimplemented!(),
"EV" => parse_propval(parse_simple_text().map(Property::EventName))(input)?, "EV" => Property::EventName(values.join("")),
"GN" => parse_propval(parse_simple_text().map(Property::GameName))(input)?, "GN" => Property::GameName(values.join("")),
"GC" => parse_propval(parse_simple_text().map(Property::ExtraGameInformation))(input)?, "GC" => Property::ExtraGameInformation(values.join("")),
"ON" => parse_propval(parse_simple_text().map(Property::GameOpening))(input)?, "ON" => Property::GameOpening(values.join("")),
"OT" => parse_propval(parse_simple_text().map(Property::Overtime))(input)?, "OT" => Property::Overtime(values.join("")),
"PB" => parse_propval(parse_simple_text().map(Property::BlackPlayer))(input)?, "PB" => Property::BlackPlayer(values.join("")),
"PC" => parse_propval(parse_simple_text().map(Property::GameLocation))(input)?, "PC" => Property::GameLocation(values.join("")),
"PW" => parse_propval(parse_simple_text().map(Property::WhitePlayer))(input)?, "PW" => Property::WhitePlayer(values.join("")),
"RE" => unimplemented!(), "RE" => Property::Result(GameResult::try_from(values.join("").as_ref()).unwrap()),
"RO" => parse_propval(parse_simple_text().map(Property::Round))(input)?, "RO" => Property::Round(values.join("")),
"RU" => parse_propval(parse_simple_text().map(Property::Ruleset))(input)?, "RU" => Property::Ruleset(values.join("")),
"SO" => parse_propval(parse_simple_text().map(Property::Source))(input)?, "SO" => Property::Source(values.join("")),
"TM" => unimplemented!(), "TM" => unimplemented!(),
"US" => parse_propval(parse_simple_text().map(Property::User))(input)?, "US" => Property::User(values.join("")),
"WR" => parse_propval(parse_simple_text().map(Property::WhiteRank))(input)?, "WR" => Property::WhiteRank(values.join("")),
"WT" => parse_propval(parse_simple_text().map(Property::WhiteTeam))(input)?, "WT" => Property::WhiteTeam(values.join("")),
_ => parse_propval(parse_simple_text().map(|value| { _ => Property::Unknown(UnknownProperty {
Property::Unknown(UnknownProperty {
ident: ident.to_owned(), ident: ident.to_owned(),
value, values,
}) }),
}))(input)?,
}; };
Ok((input, prop)) Ok((input, prop))
} }
fn parse_comment<'a, E: nom::error::ParseError<&'a str>>() -> impl Parser<&'a str, Property, E> {
parse_text().map(|text| Property::Comment(text))
}
fn parse_move<'a, E: nom::error::ParseError<&'a str>>(
color: Color,
) -> impl FnMut(&'a str) -> IResult<&'a str, Property, E> {
{
let color = color.clone();
move |input: &'a str| {
take_until1("]")
.map(|text: &'a str| Property::Move((color.clone(), text.to_owned())))
.parse(input)
}
}
}
fn parse_propval<'a, E: nom::error::ParseError<&'a str>>( fn parse_propval<'a, E: nom::error::ParseError<&'a str>>(
mut parser: impl Parser<&'a str, Property, E>, input: &'a str,
) -> impl FnMut(&'a str) -> IResult<&'a str, Property, E> { ) -> IResult<&'a str, String, E> {
move |input| {
let (input, _) = multispace0(input)?; let (input, _) = multispace0(input)?;
let (input, _) = tag("[")(input)?; let (input, _) = tag("[")(input)?;
let (input, value) = parser.parse(input)?; let (input, value) = parse_propval_text(input)?;
let (input, _) = tag("]")(input)?; let (input, _) = tag("]")(input)?;
Ok((input, value)) Ok((input, value.unwrap_or(String::new())))
}
} }
fn discard_propval<'a, E: nom::error::ParseError<&'a str>>() -> impl Parser<&'a str, (), E> { fn parse_propval_text<'a, E: nom::error::ParseError<&'a str>>(
|input| { input: &'a str,
let (input, _) = multispace0(input)?; ) -> IResult<&'a str, Option<String>, E> {
let (input, _) = tag("[")(input)?;
let (input, _) = parse_text().parse(input)?;
let (input, _) = tag("]")(input)?;
Ok((input, ()))
}
}
fn parse_number<'a, E: ParseError<&'a str>>() -> impl Parser<&'a str, i32, E> {
|input| {
let (input, sign) = opt(alt((tag("+"), tag("-"))))(input)?;
let (input, value) = digit1(input)?;
let mult = if sign == Some("-") { -1 } else { 1 };
Ok((input, value.parse::<i32>().unwrap() * mult))
}
}
fn parse_real<'a, E: ParseError<&'a str>>() -> impl Parser<&'a str, f32, E> {
|input| unimplemented!()
}
fn parse_double<'a, E: ParseError<&'a str>>() -> impl Parser<&'a str, Double, E> {
|input| unimplemented!()
}
fn parse_simple_text<'a, E: ParseError<&'a str>>() -> impl Parser<&'a str, String, E> {
|input| unimplemented!()
}
fn parse_text<'a, E: ParseError<&'a str>>() -> impl Parser<&'a str, String, E> {
|input| {
let (input, value) = opt(escaped_transform( let (input, value) = opt(escaped_transform(
none_of("\\]"), none_of("\\]"),
'\\', '\\',
@ -681,8 +643,7 @@ fn parse_text<'a, E: ParseError<&'a str>>() -> impl Parser<&'a str, String, E> {
value("", tag("\n")), value("", tag("\n")),
)), )),
))(input)?; ))(input)?;
Ok((input, value.unwrap_or("".to_owned()))) Ok((input, value.map(|v| v.to_owned())))
}
} }
/* /*
@ -714,7 +675,13 @@ mod test {
#[test] #[test]
fn it_can_parse_properties() { fn it_can_parse_properties() {
let (_, prop) = parse_property::<nom::error::VerboseError<&str>>("C[a]").unwrap(); let (_, prop) = parse_property::<nom::error::VerboseError<&str>>("C[a]").unwrap();
assert_eq!(prop, Property::Comment("a".to_owned())); assert_eq!(prop, Property::Comment(vec!["a".to_owned()]));
let (_, prop) = parse_property::<nom::error::VerboseError<&str>>("C[a][b][c]").unwrap();
assert_eq!(
prop,
Property::Comment(vec!["a".to_owned(), "b".to_owned(), "c".to_owned()])
);
} }
#[test] #[test]
@ -724,7 +691,7 @@ mod test {
assert_eq!( assert_eq!(
node, node,
Node { Node {
properties: vec![Property::Move((Color::Black, "ab".to_owned()))], properties: vec![Property::Move((Color::Black, Position("ab".to_owned())))],
next: vec![] next: vec![]
} }
); );
@ -736,13 +703,13 @@ mod test {
assert_eq!( assert_eq!(
node, node,
Node { Node {
properties: vec![Property::Move((Color::Black, "ab".to_owned()))], properties: vec![Property::Move((Color::Black, Position("ab".to_owned())))],
next: vec![Node { next: vec![Node {
properties: vec![Property::Move((Color::White, "dp".to_owned()))], properties: vec![Property::Move((Color::White, Position("dp".to_owned())))],
next: vec![Node { next: vec![Node {
properties: vec![ properties: vec![
Property::Move((Color::Black, "pq".to_owned())), Property::Move((Color::Black, Position("pq".to_owned()))),
Property::Comment("some comments".to_owned()) Property::Comment(vec!["some comments".to_owned()])
], ],
next: vec![], next: vec![],
}] }]
@ -760,13 +727,13 @@ mod test {
assert_eq!( assert_eq!(
sequence, sequence,
Node { Node {
properties: vec![Property::Move((Color::Black, "ab".to_owned()))], properties: vec![Property::Move((Color::Black, Position("ab".to_owned())))],
next: vec![Node { next: vec![Node {
properties: vec![Property::Move((Color::White, "dp".to_owned()))], properties: vec![Property::Move((Color::White, Position("dp".to_owned())))],
next: vec![Node { next: vec![Node {
properties: vec![ properties: vec![
Property::Move((Color::Black, "pq".to_owned())), Property::Move((Color::Black, Position("pq".to_owned()))),
Property::Comment("some comments".to_owned()) Property::Comment(vec!["some comments".to_owned()])
], ],
next: vec![], next: vec![],
}] }]
@ -781,18 +748,18 @@ mod test {
let (_, tree) = parse_tree::<nom::error::VerboseError<&str>>(text).unwrap(); let (_, tree) = parse_tree::<nom::error::VerboseError<&str>>(text).unwrap();
let expected = Node { let expected = Node {
properties: vec![Property::Comment("a".to_owned())], properties: vec![Property::Comment(vec!["a".to_owned()])],
next: vec![Node { next: vec![Node {
properties: vec![Property::Comment("b".to_owned())], properties: vec![Property::Comment(vec!["b".to_owned()])],
next: vec![ next: vec![
Node { Node {
properties: vec![Property::Comment("c".to_owned())], properties: vec![Property::Comment(vec!["c".to_owned()])],
next: vec![], next: vec![],
}, },
Node { Node {
properties: vec![Property::Comment("d".to_owned())], properties: vec![Property::Comment(vec!["d".to_owned()])],
next: vec![Node { next: vec![Node {
properties: vec![Property::Comment("e".to_owned())], properties: vec![Property::Comment(vec!["e".to_owned()])],
next: vec![], next: vec![],
}], }],
}, },
@ -808,49 +775,49 @@ mod test {
let (_, tree) = parse_tree::<nom::error::VerboseError<&str>>(EXAMPLE).unwrap(); let (_, tree) = parse_tree::<nom::error::VerboseError<&str>>(EXAMPLE).unwrap();
let j = Node { let j = Node {
properties: vec![Property::Comment("j".to_owned())], properties: vec![Property::Comment(vec!["j".to_owned()])],
next: vec![], next: vec![],
}; };
let i = Node { let i = Node {
properties: vec![Property::Comment("i".to_owned())], properties: vec![Property::Comment(vec!["i".to_owned()])],
next: vec![], next: vec![],
}; };
let h = Node { let h = Node {
properties: vec![Property::Comment("h".to_owned())], properties: vec![Property::Comment(vec!["h".to_owned()])],
next: vec![i], next: vec![i],
}; };
let g = Node { let g = Node {
properties: vec![Property::Comment("g".to_owned())], properties: vec![Property::Comment(vec!["g".to_owned()])],
next: vec![h], next: vec![h],
}; };
let f = Node { let f = Node {
properties: vec![Property::Comment("f".to_owned())], properties: vec![Property::Comment(vec!["f".to_owned()])],
next: vec![g, j], next: vec![g, j],
}; };
let e = Node { let e = Node {
properties: vec![Property::Comment("e".to_owned())], properties: vec![Property::Comment(vec!["e".to_owned()])],
next: vec![], next: vec![],
}; };
let d = Node { let d = Node {
properties: vec![Property::Comment("d".to_owned())], properties: vec![Property::Comment(vec!["d".to_owned()])],
next: vec![e], next: vec![e],
}; };
let c = Node { let c = Node {
properties: vec![Property::Comment("c".to_owned())], properties: vec![Property::Comment(vec!["c".to_owned()])],
next: vec![], next: vec![],
}; };
let b = Node { let b = Node {
properties: vec![Property::Comment("b".to_owned())], properties: vec![Property::Comment(vec!["b".to_owned()])],
next: vec![c, d], next: vec![c, d],
}; };
let a = Node { let a = Node {
properties: vec![Property::Comment("a".to_owned())], properties: vec![Property::Comment(vec!["a".to_owned()])],
next: vec![b], next: vec![b],
}; };
let expected = Node { let expected = Node {
properties: vec![ properties: vec![
Property::FileFormat(4), Property::FileFormat(4),
Property::Comment("root".to_owned()), Property::Comment(vec!["root".to_owned()]),
], ],
next: vec![a, f], next: vec![a, f],
}; };
@ -872,59 +839,58 @@ mod test {
#[test] #[test]
fn it_parses_propvals() { fn it_parses_propvals() {
let (_, propval) = parse_propval::<nom::error::VerboseError<&str>>(parse_comment()) let (_, propval) = parse_propval::<nom::error::VerboseError<&str>>("[]").unwrap();
.parse("[]") assert_eq!(propval, "".to_owned());
.unwrap();
assert_eq!(propval, Property::Comment("".to_owned()));
let (_, propval) = parse_propval::<nom::error::VerboseError<&str>>(parse_comment()) let (_, propval) =
.parse("[normal propval]") parse_propval::<nom::error::VerboseError<&str>>("[normal propval]").unwrap();
.unwrap(); assert_eq!(propval, "normal propval".to_owned());
assert_eq!(propval, Property::Comment("normal propval".to_owned()));
let (_, propval) = parse_propval::<nom::error::VerboseError<&str>>(parse_comment()) let (_, propval) =
.parse(r"[need an [escape\] in the propval]") parse_propval::<nom::error::VerboseError<&str>>(r"[need an [escape\] in the propval]")
.unwrap(); .unwrap();
assert_eq!( assert_eq!(propval, "need an [escape] in the propval".to_owned());
propval,
Property::Comment("need an [escape] in the propval".to_owned())
);
} }
#[test] #[test]
fn it_parses_propvals_with_hard_linebreaks() { fn it_parses_propvals_with_hard_linebreaks() {
let (_, propval) = parse_text::<nom::error::VerboseError<&str>>() let (_, propval) = parse_propval_text::<nom::error::VerboseError<&str>>(
.parse(
"There are hard linebreaks & soft linebreaks. "There are hard linebreaks & soft linebreaks.
Soft linebreaks...", Soft linebreaks...",
) )
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
propval, propval,
Some(
"There are hard linebreaks & soft linebreaks. "There are hard linebreaks & soft linebreaks.
Soft linebreaks..." Soft linebreaks..."
.to_owned()
)
); );
} }
#[test] #[test]
fn it_parses_propvals_with_escaped_closing_brackets() { fn it_parses_propvals_with_escaped_closing_brackets() {
let (_, propval) = parse_text::<nom::error::VerboseError<&str>>() let (_, propval) =
.parse(r"escaped closing \] bracket") parse_propval_text::<nom::error::VerboseError<&str>>(r"escaped closing \] bracket")
.unwrap(); .unwrap();
assert_eq!(propval, r"escaped closing ] bracket".to_owned()); assert_eq!(
propval,
Some(r"escaped closing ] bracket".to_owned()).to_owned()
);
} }
#[test] #[test]
fn it_parses_propvals_with_soft_linebreaks() { fn it_parses_propvals_with_soft_linebreaks() {
let (_, propval) = parse_text::<nom::error::VerboseError<&str>>() let (_, propval) = parse_propval_text::<nom::error::VerboseError<&str>>(
.parse(
r"Soft linebreaks are linebreaks preceeded by '\\' like this one >o\ r"Soft linebreaks are linebreaks preceeded by '\\' like this one >o\
k<. Hard line breaks are all other linebreaks.", k<. Hard line breaks are all other linebreaks.",
) )
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
propval, propval,
"Soft linebreaks are linebreaks preceeded by '\\' like this one >ok<. Hard line breaks are all other linebreaks." Some("Soft linebreaks are linebreaks preceeded by '\\' like this one >ok<. Hard line breaks are all other linebreaks.".to_owned())
.to_owned()
); );
} }