From bfc44c331a77d8c341c076e72df5ed0b56fbd422 Mon Sep 17 00:00:00 2001 From: Ryan Leckey Date: Thu, 1 Jun 2017 23:22:04 -0700 Subject: Move things around and get some tests in place --- src/path/mod.rs | 41 ++++++++++++++++++ src/path/parser.rs | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 src/path/mod.rs create mode 100644 src/path/parser.rs (limited to 'src/path') diff --git a/src/path/mod.rs b/src/path/mod.rs new file mode 100644 index 0000000..f889283 --- /dev/null +++ b/src/path/mod.rs @@ -0,0 +1,41 @@ +use std::str::FromStr; +use nom::ErrorKind; +use error::*; +use value::{Value, ValueKind}; + +mod parser; + +#[derive(Debug, Eq, PartialEq, Clone, Hash)] +pub enum Expression { + Identifier(String), + Child(Box, String), + Subscript(Box, i32), +} + +impl FromStr for Expression { + type Err = ConfigError; + + fn from_str(s: &str) -> Result { + parser::from_str(s).map_err(|kind| ConfigError::PathParse(kind)) + } +} + +impl Expression { + pub fn get<'a>(self, root: &'a Value) -> Option<&'a Value> { + match self { + Expression::Identifier(id) => { + match root.kind { + // `x` access on a table is equivalent to: map[x] + ValueKind::Table(ref map) => map.get(&id), + + // all other variants return None + _ => None, + } + } + + _ => { + unimplemented!(); + } + } + } +} diff --git a/src/path/parser.rs b/src/path/parser.rs new file mode 100644 index 0000000..eea4343 --- /dev/null +++ b/src/path/parser.rs @@ -0,0 +1,120 @@ +use nom::*; +use std::str::{FromStr, from_utf8}; +use super::Expression; + +named!(ident_, + map!( + map_res!(is_a!( + "abcdefghijklmnopqrstuvwxyz \ + ABCDEFGHIJKLMNOPQRSTUVWXYZ \ + 0123456789 \ + _-" + ), from_utf8), + |s: &str| { + s.to_string() + } + ) +); + +named!(integer , + map_res!( + map_res!( + ws!(digit), + from_utf8 + ), + FromStr::from_str + ) +); + +named!(ident, map!(ident_, Expression::Identifier)); + +fn postfix(expr: Expression) -> Box IResult<&[u8], Expression>> { + return Box::new(move |i: &[u8]| { + alt!(i, + do_parse!( + tag!(".") >> + id: ident_ >> + (Expression::Child(Box::new(expr.clone()), id)) + ) | + delimited!( + char!('['), + do_parse!( + negative: opt!(tag!("-")) >> + num: integer >> + (Expression::Subscript( + Box::new(expr.clone()), + num * (if negative.is_none() { 1 } else { -1 }) + )) + ), + char!(']') + ) + ) + }); +} + +pub fn from_str(input: &str) -> Result { + match ident(input.as_bytes()) { + IResult::Done(mut rem, mut expr) => { + while rem.len() > 0 { + match postfix(expr)(rem) { + IResult::Done(rem_, expr_) => { + rem = rem_; + expr = expr_; + } + + // Forward Incomplete and Error + result @ _ => { + return result.to_result(); + } + } + } + + Ok(expr) + } + + // Forward Incomplete and Error + result @ _ => result.to_result(), + } +} + +#[cfg(test)] +mod test { + use super::*; + use super::Expression::*; + + #[test] + fn test_id() { + let parsed: Expression = from_str("abcd").unwrap(); + assert_eq!(parsed, Identifier("abcd".into())); + } + + #[test] + fn test_id_dash() { + let parsed: Expression = from_str("abcd-efgh").unwrap(); + assert_eq!(parsed, Identifier("abcd-efgh".into())); + } + + #[test] + fn test_child() { + let parsed: Expression = from_str("abcd.efgh").unwrap(); + let expected = Child(Box::new(Identifier("abcd".into())), "efgh".into()); + + assert_eq!(parsed, expected); + } + + #[test] + fn test_subscript() { + let parsed: Expression = from_str("abcd[12]").unwrap(); + let expected = Subscript(Box::new(Identifier("abcd".into())), 12); + + assert_eq!(parsed, expected); + } + + #[test] + fn test_subscript_neg() { + let parsed: Expression = from_str("abcd[-1]").unwrap(); + let expected = Subscript(Box::new(Identifier("abcd".into())), -1); + + assert_eq!(parsed, expected); + } +} -- cgit v1.2.3 From c26907b3ecf2b139fe61bf1403c952b85285ae02 Mon Sep 17 00:00:00 2001 From: Ryan Leckey Date: Sat, 3 Jun 2017 01:21:10 -0700 Subject: Add set and set_default (and deep merging) --- src/path/mod.rs | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) (limited to 'src/path') diff --git a/src/path/mod.rs b/src/path/mod.rs index f889283..d2df442 100644 --- a/src/path/mod.rs +++ b/src/path/mod.rs @@ -1,4 +1,5 @@ use std::str::FromStr; +use std::collections::HashMap; use nom::ErrorKind; use error::*; use value::{Value, ValueKind}; @@ -33,6 +34,125 @@ impl Expression { } } + Expression::Child(expr, key) => { + match expr.get(root) { + Some(value) => { + match value.kind { + // Access on a table is identical to Identifier, it just forwards + ValueKind::Table(ref map) => map.get(&key), + + // all other variants return None + _ => None, + } + } + + _ => None, + } + } + + _ => { + unimplemented!(); + } + } + } + + pub fn get_mut<'a>(&self, root: &'a mut Value) -> Option<&'a mut Value> { + match *self { + Expression::Identifier(ref id) => { + match root.kind { + ValueKind::Table(ref mut map) => { + Some(map.entry(id.clone()).or_insert(Value::new(None, ValueKind::Nil))) + } + + _ => None, + } + } + + Expression::Child(ref expr, ref key) => { + match expr.get_mut(root) { + Some(value) => { + match value.kind { + ValueKind::Table(ref mut map) => { + Some(map.entry(key.clone()).or_insert(Value::new(None, ValueKind::Nil))) + } + + _ => { + *value = HashMap::::new().into(); + + if let ValueKind::Table(ref mut map) = value.kind { + Some(map.entry(key.clone()).or_insert(Value::new(None, ValueKind::Nil))) + } else { + println!("WHAT THE FUCK?"); + + unreachable!(); + } + } + } + } + + _ => None, + } + } + + _ => { + unimplemented!(); + } + } + } + + pub fn set<'a>(&self, root: &'a mut Value, value: Value) { + match *self { + Expression::Identifier(ref id) => { + // Ensure that root is a table + match root.kind { + ValueKind::Table(_) => { } + + _ => { + *root = HashMap::::new().into(); + } + } + + match value.kind { + ValueKind::Table(ref incoming_map) => { + // Pull out another table + let mut target = if let ValueKind::Table(ref mut map) = root.kind { + map.entry(id.clone()).or_insert(HashMap::::new().into()) + } else { + unreachable!(); + }; + + // Continue the deep merge + for (key, val) in incoming_map { + Expression::Identifier(key.clone()).set(&mut target, val.clone()); + } + } + + _ => { + if let ValueKind::Table(ref mut map) = root.kind { + // Just do a simple set + map.insert(id.clone(), value); + } + } + } + } + + Expression::Child(ref expr, ref key) => { + if let Some(parent) = expr.get_mut(root) { + match parent.kind { + ValueKind::Table(_) => { + Expression::Identifier(key.clone()).set(parent, value); + } + + _ => { + // Didn't find a table. Oh well. Make a table and do this anyway + *parent = HashMap::::new().into(); + + Expression::Identifier(key.clone()).set(parent, value); + } + } + } + } + _ => { unimplemented!(); } -- cgit v1.2.3 From ab0d8cb9aa107c8d561f3c188e6cbf472a7df23b Mon Sep 17 00:00:00 2001 From: Ryan Leckey Date: Tue, 13 Jun 2017 02:21:46 -0700 Subject: :shirt: Fix clippy warnings --- src/path/mod.rs | 12 ++++++------ src/path/parser.rs | 11 ++++++----- 2 files changed, 12 insertions(+), 11 deletions(-) (limited to 'src/path') diff --git a/src/path/mod.rs b/src/path/mod.rs index d2df442..ff93287 100644 --- a/src/path/mod.rs +++ b/src/path/mod.rs @@ -17,12 +17,12 @@ impl FromStr for Expression { type Err = ConfigError; fn from_str(s: &str) -> Result { - parser::from_str(s).map_err(|kind| ConfigError::PathParse(kind)) + parser::from_str(s).map_err(ConfigError::PathParse) } } impl Expression { - pub fn get<'a>(self, root: &'a Value) -> Option<&'a Value> { + pub fn get(self, root: &Value) -> Option<&Value> { match self { Expression::Identifier(id) => { match root.kind { @@ -61,7 +61,7 @@ impl Expression { Expression::Identifier(ref id) => { match root.kind { ValueKind::Table(ref mut map) => { - Some(map.entry(id.clone()).or_insert(Value::new(None, ValueKind::Nil))) + Some(map.entry(id.clone()).or_insert_with(|| Value::new(None, ValueKind::Nil))) } _ => None, @@ -73,14 +73,14 @@ impl Expression { Some(value) => { match value.kind { ValueKind::Table(ref mut map) => { - Some(map.entry(key.clone()).or_insert(Value::new(None, ValueKind::Nil))) + Some(map.entry(key.clone()).or_insert_with(|| Value::new(None, ValueKind::Nil))) } _ => { *value = HashMap::::new().into(); if let ValueKind::Table(ref mut map) = value.kind { - Some(map.entry(key.clone()).or_insert(Value::new(None, ValueKind::Nil))) + Some(map.entry(key.clone()).or_insert_with(|| Value::new(None, ValueKind::Nil))) } else { println!("WHAT THE FUCK?"); @@ -116,7 +116,7 @@ impl Expression { ValueKind::Table(ref incoming_map) => { // Pull out another table let mut target = if let ValueKind::Table(ref mut map) = root.kind { - map.entry(id.clone()).or_insert(HashMap::::new().into()) + map.entry(id.clone()).or_insert_with(|| HashMap::::new().into()) } else { unreachable!(); }; diff --git a/src/path/parser.rs b/src/path/parser.rs index eea4343..ee3fb65 100644 --- a/src/path/parser.rs +++ b/src/path/parser.rs @@ -28,8 +28,9 @@ named!(integer , named!(ident, map!(ident_, Expression::Identifier)); +#[allow(cyclomatic_complexity)] fn postfix(expr: Expression) -> Box IResult<&[u8], Expression>> { - return Box::new(move |i: &[u8]| { + Box::new(move |i: &[u8]| { alt!(i, do_parse!( tag!(".") >> @@ -49,13 +50,13 @@ fn postfix(expr: Expression) -> Box IResult<&[u8], Expression>> { char!(']') ) ) - }); + }) } pub fn from_str(input: &str) -> Result { match ident(input.as_bytes()) { IResult::Done(mut rem, mut expr) => { - while rem.len() > 0 { + while !rem.is_empty() { match postfix(expr)(rem) { IResult::Done(rem_, expr_) => { rem = rem_; @@ -63,7 +64,7 @@ pub fn from_str(input: &str) -> Result { } // Forward Incomplete and Error - result @ _ => { + result => { return result.to_result(); } } @@ -73,7 +74,7 @@ pub fn from_str(input: &str) -> Result { } // Forward Incomplete and Error - result @ _ => result.to_result(), + result => result.to_result(), } } -- cgit v1.2.3