summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Scott <sam.scott89@gmail.com>2017-03-10 19:11:33 -0500
committerSam Scott <sam.scott89@gmail.com>2017-03-10 19:11:33 -0500
commit217dd144ff31af3673624f8a241f7e52995ed9ec (patch)
tree47107f54b3892d8c1e3384b40c73465cf890dc3a
parentb7cb1b9aef155fc9b7b886fc21e92e3e12be0d86 (diff)
Expand tests and examples.
Need to fix sequences.
-rw-r--r--examples/csv_vectors.rs51
-rw-r--r--src/de.rs332
-rw-r--r--tests/test_deserialize.rs181
3 files changed, 424 insertions, 140 deletions
diff --git a/examples/csv_vectors.rs b/examples/csv_vectors.rs
new file mode 100644
index 0000000..95e4398
--- /dev/null
+++ b/examples/csv_vectors.rs
@@ -0,0 +1,51 @@
+extern crate csv;
+extern crate serde;
+#[macro_use]
+extern crate serde_derive;
+extern crate serde_qs as qs;
+
+#[derive(Debug, Deserialize, Serialize)]
+struct Query {
+ #[serde(deserialize_with="from_csv")]
+ r: Vec<u8>,
+ s: u8,
+}
+
+fn main() {
+ let q = "s=12&r=1,2,3";
+ let q: Query = qs::from_str(&q).unwrap();
+ println!("{:?}", q);
+}
+
+
+fn from_csv<D>(deserializer: D) -> Result<Vec<u8>, D::Error>
+ where D: serde::Deserializer
+{
+ deserializer.deserialize_str(CSVVecVisitor)
+}
+
+/// Visits a string value of the form "v1,v2,v3" into a vector of bytes Vec<u8>
+struct CSVVecVisitor;
+
+impl serde::de::Visitor for CSVVecVisitor {
+ type Value = Vec<u8>;
+
+ fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(formatter, "a str")
+ }
+
+ fn visit_str<E>(self, s: &str) -> std::result::Result<Self::Value, E>
+ where E: serde::de::Error
+ {
+ let mut output = Vec::new();
+ let mut items = csv::Reader::from_string(s);
+ // let items = items.next_str();
+ while let csv::NextField::Data(item) = items.next_str() {
+ output.push(u8::from_str_radix(item, 10).unwrap());
+ }
+
+ Ok(output)
+ }
+
+
+}
diff --git a/src/de.rs b/src/de.rs
index 5f605a3..97339f2 100644
--- a/src/de.rs
+++ b/src/de.rs
@@ -9,6 +9,47 @@ use serde::de::value::MapDeserializer;
use std::io::Read;
use url::percent_encoding;
+///
+pub struct Config {
+ max_depth: usize,
+}
+
+impl Default for Config {
+ fn default() -> Self {
+ Config {
+ max_depth: 6,
+ }
+ }
+}
+
+impl Config {
+ pub fn max_depth(&mut self, depth: usize) {
+ self.max_depth = depth;
+ }
+}
+
+impl Config {
+ pub fn from_bytes<T: de::Deserialize>(&self, input: &[u8]) -> Result<T, Error> {
+ T::deserialize(Deserializer::with_config(self, input))
+ }
+
+ pub fn from_str<T: de::Deserialize>(&self, input: &str) -> Result<T, Error> {
+ self.from_bytes(input.as_bytes())
+ }
+
+ pub fn from_reader<T, R>(&self, mut reader: R) -> Result<T, Error>
+ where T: de::Deserialize, R: Read
+ {
+ let mut buf = vec![];
+ reader.read_to_end(&mut buf)
+ .map_err(|e| {
+ de::Error::custom(format_args!("could not read input: {}", e))
+ })?;
+ self.from_bytes(&buf)
+ // from_bytes(&buf)
+ // T::deserialize(Deserializer::with_config(self, input.as_bytes()))
+ }
+}
/// Deserializes a query-string from a `&[u8]`.
///
/// ```
@@ -35,7 +76,7 @@ use url::percent_encoding;
/// # }
/// ```
pub fn from_bytes<T: de::Deserialize>(input: &[u8]) -> Result<T, Error> {
- T::deserialize(Deserializer::new(input))
+ Config::default().from_bytes(input)
}
/// Deserializes a query-string from a `&str`.
@@ -113,6 +154,7 @@ struct Parser<I: Iterator<Item=u8>> {
inner: I,
acc: Vec<u8>,
peeked: Option<u8>,
+ depth: usize,
}
impl<I: Iterator<Item=u8>> Iterator for Parser<I>
@@ -125,14 +167,6 @@ impl<I: Iterator<Item=u8>> Iterator for Parser<I>
}
impl<I: Iterator<Item=u8>> Parser<I> {
- fn new(iter: I) -> Self {
- Parser {
- inner: iter,
- acc: Vec::new(),
- peeked: None,
- }
- }
-
#[inline]
fn peek(&mut self) -> Option<<Self as Iterator>::Item> {
if !self.acc.is_empty() {
@@ -148,99 +182,136 @@ impl<I: Iterator<Item=u8>> Parser<I> {
}
}
- fn parse_string_key(&mut self, end_on: u8, consume: bool) -> Result<String, Error> {
+ fn parse_key(&mut self, end_on: u8, consume: bool) -> Result<String, Error> {
loop {
- match tu!(self.next()) {
- x if x == end_on => {
- let res = String::from_utf8(self.acc.split_off(0));
- self.acc.clear();
-
- // Add this character back to the buffer for peek.
- if !consume {
+ if let Some(x) = self.next() {
+ match x {
+ x if x == end_on => {
+ let res = String::from_utf8(self.acc.split_off(0));
+ self.acc.clear();
+
+ // Add this character back to the buffer for peek.
+ if !consume {
+ self.acc.push(x);
+ self.peeked = Some(x);
+ }
+ return res.map_err(|_| de::Error::custom("blah"))
+ },
+ b'=' => {
+ // Allow the '=' byte when parsing keys within []
+ if end_on == b']' {
+ self.acc.push(b'=');
+ } else {
+ let res = String::from_utf8(self.acc.split_off(0));
+ self.acc.clear();
+
+ // Add this character back to the buffer for peek.
+ self.acc.push(b'=');
+ self.peeked = Some(b'=');
+
+ return res.map_err(|_| de::Error::custom("blah"))
+ }
+ }
+ // x @ b']' | x @ b'[' => {
+ // return Err(de::Error::custom(format!("unexpected character {} in query string, waiting for: {}.", x as char, end_on as char)));
+ // }
+ b' ' => {
+ self.acc.push(b' ');
+ }
+ b'&' => {
+ let res = String::from_utf8(self.acc.split_off(0));
+ self.acc.clear();
+ self.acc.push(b'&');
+ self.peeked = Some(b'&');
+ return res.map_err(|_| de::Error::custom("blah"))
+ }
+ x @ 0x20 ... 0x7e => {
self.acc.push(x);
- self.peeked = Some(x);
+ },
+ _ => {
+ return Err(de::Error::custom("unexpected character in query string."));
}
- return res.map_err(|_| de::Error::custom("blah"))
- },
- x @ b'=' => {
- let res = String::from_utf8(self.acc.split_off(0));
- self.acc.clear();
-
- // Add this character back to the buffer for peek.
- self.acc.push(x);
- self.peeked = Some(x);
- return res.map_err(|_| de::Error::custom("blah"))
- }
- x @ b']' | x @ b'[' => {
- return Err(de::Error::custom(format!("unexpected character {} in query string, waiting for: {}.", x as char, end_on as char)));
- }
- x @ 0x20 ... 0x7e => {
- self.acc.push(x);
- },
- _ => {
- return Err(de::Error::custom("unexpected character in query string."));
}
+ } else {
+ // End of string.
+ let res = String::from_utf8(self.acc.split_off(0));
+ self.acc.clear();
+ return res.map_err(|_| de::Error::custom("blah"))
}
}
}
- fn parse_int_key(&mut self, end_on: u8) -> Result<String, Error> {
- loop {
- match tu!(self.next()) {
- x if x == end_on => {
- let res = String::from_utf8(self.acc.split_off(0)).unwrap();
+ fn parse_map_value(&mut self, key: String, node: &mut Level) -> Result<(), Error> {
+ if let Some(x) = self.peek() {
+ match x {
+ b'=' => {
self.acc.clear();
- return Ok(res);
+ for b in self.inner.by_ref().take_while(|b| b != &b'&') {
+ if b == b'+' {
+ self.acc.push(b' ');
+ } else {
+ self.acc.push(b);
+ }
+ }
+ let value = String::from_utf8(self.acc.split_off(0));
+ let value = value.map_err(|_e| de::Error::custom("blah"))?;
+ // Reached the end of the key string
+ if let Level::Nested(ref mut map) = *node {
+ match map.entry(key) {
+ Entry::Occupied(mut o) => {
+ o.insert(Level::Invalid("Multiple values for one key"));
+ },
+ Entry::Vacant(vm) => {
+ vm.insert(Level::Flat(value));
+ }
+ }
+ } else {
+ panic!("");
+ }
+ Ok(())
},
- x @ b'[' => {
- return Err(de::Error::custom(format!("unexpected character {} in query string, waiting for: {}.", x as char, end_on as char)));
+ b'&' => {
+ if let Level::Nested(ref mut map) = *node {
+ match map.entry(key) {
+ Entry::Occupied(mut o) => {
+ o.insert(Level::Invalid("Multiple values for one key"));
+ },
+ Entry::Vacant(vm) => {
+ vm.insert(Level::Flat("".into()));
+ }
+ }
+ } else {
+ panic!("");
+ }
+ Ok(())
}
- x @ b'0' ... b'9' => {
- self.acc.push(x);
- },
_ => {
- return Err(de::Error::custom("unexpected character in query string."));
+ // Ok(())
+ if let Level::Nested(ref mut map) = *node {
+ self.depth -= 1;
+ self.parse(
+ map.entry(key).or_insert(Level::Nested(BTreeMap::default()))
+ )?;
+ Ok(())
+ } else {
+ Ok(())
+ }
}
}
- }
-
- }
-
- fn parse_map_value(&mut self, key: String, node: &mut Level) -> Result<(), Error> {
- match tu!(self.peek()) {
- b'=' => {
- self.acc.clear();
- for b in self.inner.by_ref().take_while(|b| b != &b'&') {
- self.acc.push(b);
- }
- let value = String::from_utf8(self.acc.split_off(0));
- let value = value.map_err(|_e| de::Error::custom("blah"))?;
- // Reached the end of the key string
- if let Level::Nested(ref mut map) = *node {
- match map.entry(key) {
- Entry::Occupied(mut o) => {
- o.insert(Level::Invalid("Multiple values for one key"));
- },
- Entry::Vacant(vm) => {
- vm.insert(Level::Flat(value));
- }
+ } else {
+ if let Level::Nested(ref mut map) = *node {
+ match map.entry(key) {
+ Entry::Occupied(mut o) => {
+ o.insert(Level::Invalid("Multiple values for one key"));
+ },
+ Entry::Vacant(vm) => {
+ vm.insert(Level::Flat("".into()));
}
- } else {
- panic!("");
- }
- Ok(())
- },
- _ => {
- // Ok(())
- if let Level::Nested(ref mut map) = *node {
- self.parse(
- map.entry(key).or_insert(Level::Nested(BTreeMap::default()))
- )?;
- Ok(())
- } else {
- Ok(())
}
+ } else {
+ panic!("");
}
+ Ok(())
}
}
@@ -272,45 +343,53 @@ impl<I: Iterator<Item=u8>> Parser<I> {
fn parse(&mut self, node: &mut Level) -> Result<bool, Error> {
// First character determines parsing type
+ if self.depth == 0 {
+ let key = self.parse_key(b'\x00', true)?;
+ self.parse_map_value(key.into(), node)?;
+ self.depth += 1;
+ return Ok(true);
+ }
match self.peek() {
Some(x) => match x {
- b'a' ... b'z' | b'A' ... b'Z' => {
- let key = self.parse_string_key(b'[', false).unwrap();
- self.parse_map_value(key.into(), node)?;
- Ok(true)
- },
b'[' => {
self.acc.clear();
// let _ = self.next();
match tu!(self.peek()) {
- b'a' ... b'z' | b'A' ... b'Z' => {
- let key = self.parse_string_key(b']', true).unwrap();
- // key.into()
- self.parse_map_value(key.into(), node)?;
- Ok(true)
+ b'[' => {
+ panic!("");
},
b']' => {
self.parse_seq_value(node)?;
+ self.depth += 1;
Ok(true)
},
- b'0' ... b'9' => {
- let key = self.parse_int_key(b']').unwrap();
- self.parse_map_value(key, node)?;
+ 0x20 ... 0x7e => {
+ let key = self.parse_key(b']', true).unwrap();
+ // key.into()
+ self.parse_map_value(key.into(), node)?;
+ self.depth += 1;
Ok(true)
+
},
_ => {
panic!("");
}
}
},
+ 0x20 ... 0x7e => {
+ let key = self.parse_key(b'[', false).unwrap();
+ self.parse_map_value(key.into(), node)?;
+ self.depth += 1;
+ Ok(true)
+ },
_ => {
panic!("");
}
},
// Ran out of characters to parse
- None => return Ok(false)
+ None => Ok(false)
}
}
@@ -325,12 +404,18 @@ impl Deserializer {
}
/// Returns a new `Deserializer`.
- pub fn new(input: &[u8]) -> Self {
+ fn with_config(config: &Config, input: &[u8]) -> Self {
let map = BTreeMap::default();
let mut root = Level::Nested(map);
let decoded = percent_encoding::percent_decode(&input);
- let mut parser = Parser::new(decoded);
+ let mut parser = Parser {
+ inner: decoded,
+ acc: Vec::new(),
+ peeked: None,
+ depth: config.max_depth,
+ };
+
while let Ok(x) = parser.parse(&mut root) {
if !x {
break
@@ -347,6 +432,30 @@ impl Deserializer {
value: None,
}
}
+
+ // /// Returns a new `Deserializer`.
+ // pub fn new(input: &[u8]) -> Self {
+ // let map = BTreeMap::default();
+ // let mut root = Level::Nested(map);
+
+ // let decoded = percent_encoding::percent_decode(&input);
+ // let mut parser = Parser::new(decoded);
+ // while let Ok(x) = parser.parse(&mut root) {
+ // if !x {
+ // break
+ // }
+ // }
+ // // self.input = Some(decoded.as_bytes());
+ // // println!("{:?}", root);
+ // let iter = match root {
+ // Level::Nested(map) => map.into_iter(),
+ // _ => panic!(""),
+ // };
+ // Deserializer {
+ // iter: iter,
+ // value: None,
+ // }
+ // }
}
impl de::Deserializer for Deserializer {
@@ -463,7 +572,7 @@ impl de::Deserializer for LevelDeserializer {
if let Level::Nested(x) = self.0 {
Deserializer::with_map(x).deserialize_map(visitor)
} else {
- Err(de::Error::custom("value does not appear to be a map"))
+ Err(de::Error::custom(format!("value: {:?} does not appear to be a map", self.0)))
}
}
@@ -504,6 +613,23 @@ impl de::Deserializer for LevelDeserializer {
self.deserialize_seq(visitor)
}
+ fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error> where V: de::Visitor
+
+ {
+ match self.0 {
+ Level::Flat(x) => {
+ if x == "" {
+ visitor.visit_none()
+ } else {
+ visitor.visit_some(x.into_deserializer())
+ }
+ }
+ _ => {
+ Err(de::Error::custom("value does not appear to be a value"))
+ }
+ }
+ }
+
forward_to_deserialize! {
bool
@@ -521,7 +647,7 @@ impl de::Deserializer for LevelDeserializer {
str
string
unit
- option
+ // option
bytes
byte_buf
unit_struct
diff --git a/tests/test_deserialize.rs b/tests/test_deserialize.rs
index fb7d67e..bac84bf 100644
--- a/tests/test_deserialize.rs
+++ b/tests/test_deserialize.rs
@@ -2,55 +2,162 @@
extern crate serde_derive;
extern crate serde_qs as qs;
+use std::collections::HashMap;
-#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
-struct A { b: B, c: C }
-#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
-struct B { b1: u8, b2: String }
-#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
-struct C { c1: String, c2: u8 }
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+struct Address {
+ city: String,
+ postcode: String,
+}
+
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+struct QueryParams {
+ id: u8,
+ name: String,
+ address: Address,
+ user_ids: Vec<u8>,
+}
+
+macro_rules! map_test {
+ ($string:expr, $($mapvars:tt)*) => {
+ let expected_map = hash_to_map!(New $($mapvars)*);
+ let testmap: HashMap<_, _> = qs::from_str($string).unwrap();
+ assert_eq!(expected_map, testmap);
+ }
+}
+
+
+macro_rules! hash_to_map {
+ // Base case: a map with no inputs, do nothing
+ ($map:expr, ) => ();
+ //{}
+ // This parses a single map entry, with a value explicitly an expression.
+ ($map:expr, $k:tt[e $v:expr] $($rest:tt)*) => {{
+ $map.insert($k.to_owned(), $v.to_owned());
+ hash_to_map!($map, $($rest)*);
+ }};
-#[derive(PartialEq, Debug, Serialize, Deserialize)]
-struct Complex { x: Vec<u8>, y: Vec<C> }
+ // This parses a single map entry, plus the rest of the values.
+ ($map:expr, $k:tt[$v:tt] $($rest:tt)*) => {{
+ $map.insert($k.to_owned(), $v.to_owned());
+ hash_to_map!($map, $($rest)*);
+ }};
+ // This parses the first entry as a nested entry, and tail calls the remaining
+ // in rest.
+ ($map:expr, $k:tt[$($inner:tt)*] $($rest:tt)*) => {{
+ let mut inner_map = HashMap::new();
+ hash_to_map!(inner_map, $($inner)*);
+ $map.insert($k.to_owned(), inner_map);
+ hash_to_map!($map, $($rest)*);
+ }};
+
+ // Constructs the map and then runs the macro. This infers the types for the
+ // hashmap.
+ (New $($rest:tt)*) => {{
+ let mut map = HashMap::new();
+ hash_to_map!(map, $($rest)*);
+ map
+ }}
+
+}
#[test]
fn deserialize_struct() {
- let params = A {
- b: B {
- b1: 10,
- b2: "Ten".to_owned()
- },
- c: C {
- c1: "Seven".to_owned(),
- c2: 7
- }
+ let params = QueryParams {
+ id: 42,
+ name: "Acme".to_string(),
+ address: Address {
+ city: "Carrot City".to_string(),
+ postcode: "12345".to_string(),
+ },
+ user_ids: vec!(1,2,3,4),
};
- let complex_params = Complex {
- x: vec![0,1,2],
- y: vec![params.c.clone()],
- };
+ let rec_params: QueryParams = qs::from_str("name=Acme&id=42&phone=12345&address[postcode]=12345&address[city]=Carrot+City&user_ids[0]=1&user_ids[1]=2&user_ids[2]=3&user_ids[3]=4").unwrap();
+ assert_eq!(rec_params, params);
+ let rec_params: QueryParams = qs::from_str("name=Acme&id=42&phone=12345&address[postcode]=12345&address[city]=Carrot+City&user_ids[]=1&user_ids[]=2&user_ids[]=3&user_ids[]=4").unwrap();
+ assert_eq!(rec_params, params);
+
+}
+
+#[test]
+fn qs_test_simple() {
+// test('parse()', function (t) {
+ // t.test('parses a simple string', function (st) {
+ // st.deepEqual(qs.parse('0=foo'), { 0: 'foo' });
+ map_test!("0=foo", 0["foo"]);
- let input = "b[b1]=10&b[b2]=Ten&c[c1]=Seven&c[c2]=7";
- let input2 = "c[c1]=Seven&b[b2]=Ten&b[b1]=10&c[c2]=7";
- let result: A = qs::from_str(&urlencode(input)).unwrap();
- assert_eq!(result, params);
- let result: A = qs::from_str(&input).unwrap();
- assert_eq!(result, params);
- let result: A = qs::from_str(&urlencode(input2)).unwrap();
- assert_eq!(result, params);
- let result: A = qs::from_str(&input2).unwrap();
- assert_eq!(result, params);
+ // st.deepEqual(qs.parse('foo=c++'), { foo: 'c ' });
+ map_test!("foo=c++", "foo"["c "]);
- let input3 = "x[0]=0&x[1]=1&x[2]=2&y[0][c1]=Seven&y[0][c2]=7";
- let result: Complex = qs::from_str(&input3).unwrap();
- assert_eq!(complex_params, result);
+ // st.deepEqual(qs.parse('a[>=]=23'), { a: { '>=': '23' } });
+ map_test!("a[>=]=23", "a"[">="[23]]);
+ // st.deepEqual(qs.parse('a[<=>]==23'), { a: { '<=>': '=23' } });
+ map_test!("a[<=>]==23", "a"["<=>"["=23"]]);
+ // st.deepEqual(qs.parse('a[==]=23'), { a: { '==': '23' } });
+ map_test!("a[==]=23", "a"["=="[23]]);
+ // st.deepEqual(qs.parse('foo', { strictNullHandling: true }), { foo: null });
+ let none: Option<String> = Option::None;
+ map_test!("foo", "foo"[none]);
+
+ // st.deepEqual(qs.parse('foo'), { foo: '' });
+ map_test!("foo", "foo"[""]);
+
+ // st.deepEqual(qs.parse('foo='), { foo: '' });
+ map_test!("foo=", "foo"[""]);
+
+ // st.deepEqual(qs.parse('foo=bar'), { foo: 'bar' });
+ map_test!("foo=bar", "foo"["bar"]);
+
+ // st.deepEqual(qs.parse(' foo = bar = baz '), { ' foo ': ' bar = baz ' });
+ map_test!(" foo = bar = baz ", " foo "[" bar = baz "]);
+
+ // st.deepEqual(qs.parse('foo=bar=baz'), { foo: 'bar=baz' });
+ map_test!("foo=bar=baz", "foo"["bar=baz"]);
+
+ // st.deepEqual(qs.parse('foo=bar&bar=baz'), { foo: 'bar', bar: 'baz' });
+ map_test!("foo=bar&bar=baz", "foo"["bar"] "bar"["baz"]);
+
+ // st.deepEqual(qs.parse('foo2=bar2&baz2='), { foo2: 'bar2', baz2: '' });
+ map_test!("foo2=bar2&baz2=", "foo2"["bar2"] "baz2"[""]);
+
+ // st.deepEqual(qs.parse('foo=bar&baz', { strictNullHandling: true }), { foo: 'bar', baz: null });
+ map_test!("foo=bar&baz", "foo"[e Some("bar".to_string())] "baz"[e None]);
+
+ // st.deepEqual(qs.parse('foo=bar&baz'), { foo: 'bar', baz: '' });
+ map_test!("foo=bar&baz", "foo"["bar"] "baz"[""]);
+
+ // st.deepEqual(qs.parse('cht=p3&chd=t:60,40&chs=250x100&chl=Hello|World'), {
+ // cht: 'p3',
+ // chd: 't:60,40',
+ // chs: '250x100',
+ // chl: 'Hello|World'
+ // });
+ map_test!("cht=p3&chd=t:60,40&chs=250x100&chl=Hello|World",
+ "cht"["p3"]
+ "chd"["t:60,40"]
+ "chs"["250x100"]
+ "chl"["Hello|World"]
+ );
+ // st.end();
+// });
}
-fn urlencode(input: &str) -> String {
- str::replace(&str::replace(input, "[", "%5B"), "]", "%5D")
-} \ No newline at end of file
+#[test]
+fn qs_nesting() {
+ // t.deepEqual(qs.parse('a[b]=c'), { a: { b: 'c' } }, 'parses a single nested string');
+ map_test!("a[b]=c", "a"["b"["c"]]);
+
+ // t.deepEqual(qs.parse('a[b][c]=d'), { a: { b: { c: 'd' } } }, 'parses a double nested string');
+ map_test!("a[b][c]=d", "a"["b"["c"["d"]]]);
+ // t.deepEqual(
+ // qs.parse('a[b][c][d][e][f][g][h]=i'),
+ // { a: { b: { c: { d: { e: { f: { '[g][h]': 'i' } } } } } } },
+ // 'defaults to a depth of 5'
+ // );
+ // map_test!("a[b][c][d][e][f][g][h]=i", "a"["b"["c"["d"["e"["f"["[g][h]"["i"]]]]]]]);
+}