summaryrefslogtreecommitdiffstats
path: root/src/path
diff options
context:
space:
mode:
authorRyan Leckey <ryan@launchbadge.com>2017-06-13 18:55:40 -0700
committerRyan Leckey <ryan@launchbadge.com>2017-06-13 18:55:40 -0700
commit47ad966e064f842056e7c1b7abe8ef64d127f5be (patch)
tree4dd4e14168383dd818fe6add8ed0a5f4900d85b6 /src/path
parent1266d110ba31a9d0f9f4908a5a30291899ffc8fc (diff)
parent312f32905f518fa59e1daf3fc8224dadf584b484 (diff)
Merge branch 'serde'
Diffstat (limited to 'src/path')
-rw-r--r--src/path/mod.rs161
-rw-r--r--src/path/parser.rs121
2 files changed, 282 insertions, 0 deletions
diff --git a/src/path/mod.rs b/src/path/mod.rs
new file mode 100644
index 0000000..ff93287
--- /dev/null
+++ b/src/path/mod.rs
@@ -0,0 +1,161 @@
+use std::str::FromStr;
+use std::collections::HashMap;
+use nom::ErrorKind;
+use error::*;
+use value::{Value, ValueKind};
+
+mod parser;
+
+#[derive(Debug, Eq, PartialEq, Clone, Hash)]
+pub enum Expression {
+ Identifier(String),
+ Child(Box<Expression>, String),
+ Subscript(Box<Expression>, i32),
+}
+
+impl FromStr for Expression {
+ type Err = ConfigError;
+
+ fn from_str(s: &str) -> Result<Expression> {
+ parser::from_str(s).map_err(ConfigError::PathParse)
+ }
+}
+
+impl Expression {
+ pub fn get(self, root: &Value) -> Option<&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,
+ }
+ }
+
+ 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_with(|| 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_with(|| Value::new(None, ValueKind::Nil)))
+ }
+
+ _ => {
+ *value = HashMap::<String, Value>::new().into();
+
+ if let ValueKind::Table(ref mut map) = value.kind {
+ Some(map.entry(key.clone()).or_insert_with(|| 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::<String, Value>::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_with(|| HashMap::<String, Value>::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::<String, Value>::new().into();
+
+ Expression::Identifier(key.clone()).set(parent, value);
+ }
+ }
+ }
+ }
+
+ _ => {
+ unimplemented!();
+ }
+ }
+ }
+}
diff --git a/src/path/parser.rs b/src/path/parser.rs
new file mode 100644
index 0000000..ee3fb65
--- /dev/null
+++ b/src/path/parser.rs
@@ -0,0 +1,121 @@
+use nom::*;
+use std::str::{FromStr, from_utf8};
+use super::Expression;
+
+named!(ident_<String>,
+ map!(
+ map_res!(is_a!(
+ "abcdefghijklmnopqrstuvwxyz \
+ ABCDEFGHIJKLMNOPQRSTUVWXYZ \
+ 0123456789 \
+ _-"
+ ), from_utf8),
+ |s: &str| {
+ s.to_string()
+ }
+ )
+);
+
+named!(integer <i32>,
+ map_res!(
+ map_res!(
+ ws!(digit),
+ from_utf8
+ ),
+ FromStr::from_str
+ )
+);
+
+named!(ident<Expression>, map!(ident_, Expression::Identifier));
+
+#[allow(cyclomatic_complexity)]
+fn postfix(expr: Expression) -> Box<Fn(&[u8]) -> IResult<&[u8], Expression>> {
+ 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<Expression, ErrorKind> {
+ match ident(input.as_bytes()) {
+ IResult::Done(mut rem, mut expr) => {
+ while !rem.is_empty() {
+ 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);
+ }
+}