Compare commits

...

2 Commits

3 changed files with 255 additions and 266 deletions

View File

@ -1,4 +1,4 @@
use crate::{tree, Color, Position}; use crate::{parser, Color};
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<&tree::Node> for GameNode { impl TryFrom<&parser::Node> for GameNode {
type Error = ConversionError; type Error = ConversionError;
fn try_from(n: &tree::Node) -> Result<Self, Self::Error> { fn try_from(n: &parser::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: Position, position: String,
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: Position) -> Self { pub fn new(color: Color, position: String) -> Self {
Self { Self {
id: Uuid::new_v4(), id: Uuid::new_v4(),
color, color,
@ -304,42 +304,41 @@ impl Node for MoveNode {
} }
} }
impl TryFrom<&tree::Node> for MoveNode { impl TryFrom<&parser::Node> for MoveNode {
type Error = ConversionError; type Error = ConversionError;
fn try_from(n: &tree::Node) -> Result<Self, Self::Error> { fn try_from(n: &parser::Node) -> Result<Self, Self::Error> {
let move_ = match (n.find_prop("W"), n.find_prop("B")) { match n.move_() {
(Some(white_move), _) => Some((Color::White, Position{ row: white_move Some((color, position)) => Ok(MoveNode::new(color, position)),
(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{ row: s[0], column: s[1] }) Ok(Position {
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>, Position)>, positions: Vec<(Option<Color>, String)>,
children: Vec<GameNode>, children: Vec<GameNode>,
} }
impl SetupNode { impl SetupNode {
pub fn new(positions: Vec<(Option<Color>, Position)>) -> Result<Self, SetupError> { pub fn new(positions: Vec<(Option<Color>, String)>) -> Result<Self, SetupError> {
let mut coords: HashSet<Position> = HashSet::new(); let mut coords: HashSet<String> = 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);
@ -396,21 +395,9 @@ 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( let first_move = MoveNode::new(Color::Black, "dd".to_owned());
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( let second_move = MoveNode::new(Color::White, "qq".to_owned());
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();
@ -429,20 +416,19 @@ mod test {
#[test] #[test]
fn game_node_can_parse_sgf_move_node() { fn game_node_can_parse_sgf_move_node() {
let n = tree::Node { let n = parser::Node {
properties: vec![ properties: vec![
tree::Property { parser::Property::Move((Color::White, "dp".to_owned())),
ident: "W".to_owned(), /*
values: vec!["dp".to_owned()], parser::Property {
},
tree::Property {
ident: "WL".to_owned(), ident: "WL".to_owned(),
values: vec!["176.099".to_owned()], values: vec!["176.099".to_owned()],
}, },
tree::Property { parser::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![],
}; };
@ -472,26 +458,25 @@ mod move_node_tests {
#[test] #[test]
fn it_can_parse_an_sgf_move_node() { fn it_can_parse_an_sgf_move_node() {
let n = tree::Node { let n = parser::Node {
properties: vec![ properties: vec![
tree::Property { parser::Property::Move((Color::White, "dp".to_owned())),
ident: "W".to_owned(), /*
values: vec!["dp".to_owned()], parser::Property {
},
tree::Property {
ident: "WL".to_owned(), ident: "WL".to_owned(),
values: vec!["176.099".to_owned()], values: vec!["176.099".to_owned()],
}, },
tree::Property { parser::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, Position{ row: 'd', column: 'p' }); assert_eq!(node.position, "dp".to_owned());
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()]);
@ -517,46 +502,16 @@ 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), (Some(Color::Black), "dd".to_owned(),),
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), (Some(Color::Black), "ee".to_owned(),),
Position { (Some(Color::White), "ee".to_owned(),),
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 tree; mod game;
use tree::{parse_collection, Tree};
// mod game; mod parser;
use parser::{parse_collection, Tree};
use thiserror::Error; use thiserror::Error;

View File

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