From 6d79b7a7fabbf57bb30278c95fbd6b9988f443ad Mon Sep 17 00:00:00 2001 From: vorboyvo Date: Tue, 23 Sep 2025 14:51:26 -0400 Subject: [PATCH] Wrote parser and added tests for it. Refactored tests a little. --- Cargo.lock | 32 ------------- Cargo.toml | 1 + src/main.rs | 127 ++++++++++++++++++++++++++++++++++++++------------ src/parser.rs | 39 ++++++++++++++++ 4 files changed, 138 insertions(+), 61 deletions(-) delete mode 100644 Cargo.lock create mode 100644 src/parser.rs diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index fb83e0f..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,32 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "assert_approx_eq" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c07dab4369547dbe5114677b33fbbf724971019f3818172d59a97a61c774ffd" - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "code" -version = "0.1.0" -dependencies = [ - "assert_approx_eq", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] diff --git a/Cargo.toml b/Cargo.toml index 062a56c..23dd6e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,5 @@ edition = "2024" [dependencies] assert_approx_eq = "1.1.0" +nom = "8.0.0" num-traits = "0.2" diff --git a/src/main.rs b/src/main.rs index c803791..af2531a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,7 @@ use num_traits::Float; +mod parser; + /* * NOTES * - each alternative should appear only once in a tree. need to figure out a @@ -7,22 +9,25 @@ use num_traits::Float; * - looks like this is like quite difficult to do through the type system */ -#[derive(PartialEq, Eq)] -struct Alternative { +#[derive(PartialEq, Eq, Debug)] +pub struct Alternative { name: T } -struct Edge { +#[derive(PartialEq, Eq, Debug)] +pub struct Edge { weight: U, destination: Tree } -enum Vertex { +#[derive(PartialEq, Eq, Debug)] +pub enum Vertex { NonTerminal(Box>, Box>), Terminal(Alternative), } -struct Tree { +#[derive(PartialEq, Eq, Debug)] +pub struct Tree { root: Vertex } @@ -84,11 +89,13 @@ mod tests { use super::*; use assert_approx_eq::assert_approx_eq; - // A test for the simplest symmetric case - #[test] - fn choice_probability_test_1() { - let a = Alternative {name: "A"}; - let b = Alternative {name: "B"}; + fn simple_symmetric_tree() -> Tree { + let a = Alternative { + name: "A".to_owned() + }; + let b = Alternative { + name: "B".to_owned() + }; let edge_a = Edge { weight: 1.0, destination: Tree {root: Vertex::Terminal(a)} @@ -98,28 +105,38 @@ mod tests { destination: Tree {root: Vertex::Terminal(b)} }; let root = Vertex::NonTerminal(Box::new(edge_a), Box::new(edge_b)); - let tree = Tree {root: root}; - assert_eq!(tree.choice_probability(&"A"), 0.5); + Tree {root: root} } - // A test for the simplest asymmetric case - #[test] - fn choice_probability_test_2() { - let a = Alternative {name: "A"}; - let b = Alternative {name: "B"}; - let edge_a = Edge {weight: 3.0, destination: Tree {root: Vertex::Terminal(a)}}; - let edge_b = Edge {weight: 1.0, destination: Tree {root: Vertex::Terminal(b)}}; + fn simple_asymmetric_tree() -> Tree { + let a = Alternative { + name: "A".to_owned() + }; + let b = Alternative { + name: "B".to_owned() + }; + let edge_a = Edge { + weight: 3.0, + destination: Tree {root: Vertex::Terminal(a)} + }; + let edge_b = Edge { + weight: 1.0, + destination: Tree {root: Vertex::Terminal(b)} + }; let root = Vertex::NonTerminal(Box::new(edge_a), Box::new(edge_b)); - let tree = Tree {root: root}; - assert_eq!(tree.choice_probability(&"A"), 0.75); + Tree {root: root} } - // A test for depth higher than 1 - #[test] - fn choice_probability_test_3() { - let a = Alternative {name: "A"}; - let b = Alternative {name: "B"}; - let c = Alternative {name: "C"}; + fn complex_tree() -> Tree { + let a = Alternative { + name: "A".to_owned() + }; + let b = Alternative { + name: "B".to_owned() + }; + let c = Alternative { + name: "C".to_owned() + }; let edge_a = Edge { weight: 2.5, destination: Tree {root: Vertex::Terminal(a)} @@ -139,10 +156,62 @@ mod tests { destination: Tree {root: Vertex::Terminal(c)} }; let root = Vertex::NonTerminal(Box::new(edge_ab), Box::new(edge_c)); - let tree = Tree {root: root}; + Tree {root: root} + } + + + // A test for the simplest symmetric case + #[test] + fn choice_probability_test_1() { + assert_eq!(simple_symmetric_tree().choice_probability(&("A".to_owned())), 0.5); + } + + // A test for the simplest asymmetric case + #[test] + fn choice_probability_test_2() { + assert_eq!(simple_asymmetric_tree().choice_probability(&("A".to_owned())), 0.75); + } + + // A test for depth higher than 1 + #[test] + fn choice_probability_test_3() { assert_approx_eq!( - tree.choice_probability(&"A"), + complex_tree().choice_probability(&("A".to_owned())), (2.5/(2.5+1.0))*((2.5+1.0+0.5)/(2.5+1.0+0.5+1.0)) ); } + + // A test for parsing the simplest symmetric case + #[test] + fn parser_test_1() { + assert_eq!( + { + let (_, b) = parser::subtree("([1.0]A[1.0]B)").unwrap(); + b + }, + simple_symmetric_tree() + ) + } + + #[test] + fn parser_test_2() { + assert_eq!( + { + let (_, b) = parser::subtree("([3.0]A[1.0]B)").unwrap(); + b + }, + simple_asymmetric_tree() + ) + } + + #[test] + fn parser_test_3() { + assert_eq!( + { + let (_, b) = parser::subtree("([0.5]([2.5]A[1.0]B)[1.0]C)").unwrap(); + b + }, + complex_tree() + ) + } } diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000..963c512 --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,39 @@ +/* + * WHAT WE ARE PARSING: + * + */ + +use nom::{branch::alt, bytes::complete::take_while1, character::char, combinator::map, error::Error, number::complete::double, sequence::{delimited, pair}, AsChar, IResult, Parser}; + +use crate::{Alternative, Edge, Tree, Vertex}; + +fn weighted_edge(input: &str) -> IResult<&str, Edge> { + let weight = delimited( + char::<&str, Error<&str>>('['), + double, + char(']') + ); + let alternative = map( + take_while1::<_, &str, Error<&str>>(AsChar::is_alpha), + |a| Tree:: { + root: Vertex::Terminal(Alternative{name: a.to_string()}) + } + ); + map( + pair(weight, alt((alternative, subtree))), + |(a, b)| Edge{ + weight: a, + destination: b + } + ).parse(input) +} + +pub fn subtree(input: &str) -> IResult<&str, Tree> { + let inner = map( + pair(weighted_edge, weighted_edge), + |(a, b)| Tree{ + root: Vertex::NonTerminal(Box::new(a), Box::new(b)) + } + ); + delimited(char('('), inner, char(')')).parse(input) +}