summaryrefslogtreecommitdiffstats
path: root/src/path
diff options
context:
space:
mode:
authorRyan Leckey <ryan@launchbadge.com>2017-06-01 23:22:04 -0700
committerRyan Leckey <ryan@launchbadge.com>2017-06-01 23:22:04 -0700
commitbfc44c331a77d8c341c076e72df5ed0b56fbd422 (patch)
treec757723957be6b880d1e0d8d26ae2b1c9c606ed2 /src/path
parent4357840e95f3646494ddeea4aae12425dfab2db8 (diff)
Move things around and get some tests in place
Diffstat (limited to 'src/path')
-rw-r--r--src/path/mod.rs41
-rw-r--r--src/path/parser.rs120
2 files changed, 161 insertions, 0 deletions
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<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(|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_<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));
+
+fn postfix(expr: Expression) -> Box<Fn(&[u8]) -> 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<Expression, ErrorKind> {
+ 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);
+ }
+}