From 11115f13a3499420cd09b745a298ef071755b24b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 20 Jun 2014 17:01:38 -0700 Subject: [PATCH] Initial commit --- src/parser.rs | 617 ++++++++++++++++++ src/test/README.md | 1 + .../array-mixed-types-ints-and-floats.json | 15 + src/test/invalid.rs | 76 +++ .../array-mixed-types-arrays-and-ints.toml | 1 + .../array-mixed-types-ints-and-floats.toml | 1 + .../array-mixed-types-strings-and-ints.toml | 1 + .../invalid/datetime-malformed-no-leads.toml | 1 + .../invalid/datetime-malformed-no-secs.toml | 1 + src/test/invalid/datetime-malformed-no-t.toml | 1 + src/test/invalid/datetime-malformed-no-z.toml | 1 + .../datetime-malformed-with-milli.toml | 1 + src/test/invalid/duplicate-key-table.toml | 5 + src/test/invalid/duplicate-keys.toml | 2 + src/test/invalid/duplicate-tables.toml | 2 + src/test/invalid/empty-implicit-table.toml | 1 + src/test/invalid/empty-table.toml | 1 + src/test/invalid/float-no-leading-zero.toml | 2 + .../invalid/float-no-trailing-digits.toml | 2 + src/test/invalid/key-two-equals.toml | 1 + src/test/invalid/string-bad-byte-escape.toml | 1 + src/test/invalid/string-bad-escape.toml | 1 + src/test/invalid/string-byte-escapes.toml | 1 + src/test/invalid/string-no-close.toml | 1 + src/test/invalid/table-array-implicit.toml | 14 + .../table-array-malformed-bracket.toml | 2 + .../invalid/table-array-malformed-empty.toml | 2 + .../invalid/table-nested-brackets-close.toml | 2 + .../invalid/table-nested-brackets-open.toml | 2 + .../invalid/text-after-array-entries.toml | 4 + src/test/invalid/text-after-integer.toml | 1 + src/test/invalid/text-after-string.toml | 1 + src/test/invalid/text-after-table.toml | 1 + .../invalid/text-before-array-separator.toml | 4 + src/test/invalid/text-in-array.toml | 5 + src/test/mod.rs | 2 + src/test/valid.rs | 163 +++++ src/test/valid/array-empty.json | 11 + src/test/valid/array-empty.toml | 1 + src/test/valid/array-nospaces.json | 10 + src/test/valid/array-nospaces.toml | 1 + src/test/valid/arrays-hetergeneous.json | 19 + src/test/valid/arrays-hetergeneous.toml | 1 + src/test/valid/arrays-nested.json | 13 + src/test/valid/arrays-nested.toml | 1 + src/test/valid/arrays.json | 34 + src/test/valid/arrays.toml | 9 + src/test/valid/bool.json | 4 + src/test/valid/bool.toml | 2 + src/test/valid/comments-everywhere.json | 12 + src/test/valid/comments-everywhere.toml | 24 + src/test/valid/datetime.json | 3 + src/test/valid/datetime.toml | 1 + src/test/valid/empty.json | 1 + src/test/valid/empty.toml | 0 src/test/valid/example.json | 14 + src/test/valid/example.toml | 5 + src/test/valid/float.json | 4 + src/test/valid/float.toml | 2 + .../valid/implicit-and-explicit-after.json | 10 + .../valid/implicit-and-explicit-after.toml | 5 + .../valid/implicit-and-explicit-before.json | 10 + .../valid/implicit-and-explicit-before.toml | 5 + src/test/valid/implicit-groups.json | 9 + src/test/valid/implicit-groups.toml | 2 + src/test/valid/integer.json | 4 + src/test/valid/integer.toml | 2 + src/test/valid/key-equals-nospace.json | 3 + src/test/valid/key-equals-nospace.toml | 1 + src/test/valid/key-special-chars.json | 5 + src/test/valid/key-special-chars.toml | 1 + src/test/valid/key-with-pound.json | 3 + src/test/valid/key-with-pound.toml | 1 + src/test/valid/long-float.json | 4 + src/test/valid/long-float.toml | 2 + src/test/valid/long-integer.json | 4 + src/test/valid/long-integer.toml | 2 + src/test/valid/string-escapes.json | 34 + src/test/valid/string-escapes.toml | 8 + src/test/valid/string-simple.json | 6 + src/test/valid/string-simple.toml | 1 + src/test/valid/string-with-pound.json | 7 + src/test/valid/string-with-pound.toml | 2 + src/test/valid/table-array-implicit.json | 7 + src/test/valid/table-array-implicit.toml | 2 + src/test/valid/table-array-many.json | 16 + src/test/valid/table-array-many.toml | 11 + src/test/valid/table-array-nest.json | 18 + src/test/valid/table-array-nest.toml | 17 + src/test/valid/table-array-one.json | 8 + src/test/valid/table-array-one.toml | 3 + src/test/valid/table-empty.json | 3 + src/test/valid/table-empty.toml | 1 + src/test/valid/table-sub-empty.json | 3 + src/test/valid/table-sub-empty.toml | 2 + src/test/valid/table-whitespace.json | 3 + src/test/valid/table-whitespace.toml | 1 + src/test/valid/table-with-pound.json | 5 + src/test/valid/table-with-pound.toml | 2 + src/test/valid/unicode-escape.json | 3 + src/test/valid/unicode-escape.toml | 1 + src/test/valid/unicode-literal.json | 3 + src/test/valid/unicode-literal.toml | 1 + src/toml.rs | 51 ++ 104 files changed, 1404 insertions(+) create mode 100644 src/parser.rs create mode 100644 src/test/README.md create mode 100644 src/test/invalid-encoder/array-mixed-types-ints-and-floats.json create mode 100644 src/test/invalid.rs create mode 100644 src/test/invalid/array-mixed-types-arrays-and-ints.toml create mode 100644 src/test/invalid/array-mixed-types-ints-and-floats.toml create mode 100644 src/test/invalid/array-mixed-types-strings-and-ints.toml create mode 100644 src/test/invalid/datetime-malformed-no-leads.toml create mode 100644 src/test/invalid/datetime-malformed-no-secs.toml create mode 100644 src/test/invalid/datetime-malformed-no-t.toml create mode 100644 src/test/invalid/datetime-malformed-no-z.toml create mode 100644 src/test/invalid/datetime-malformed-with-milli.toml create mode 100644 src/test/invalid/duplicate-key-table.toml create mode 100644 src/test/invalid/duplicate-keys.toml create mode 100644 src/test/invalid/duplicate-tables.toml create mode 100644 src/test/invalid/empty-implicit-table.toml create mode 100644 src/test/invalid/empty-table.toml create mode 100644 src/test/invalid/float-no-leading-zero.toml create mode 100644 src/test/invalid/float-no-trailing-digits.toml create mode 100644 src/test/invalid/key-two-equals.toml create mode 100644 src/test/invalid/string-bad-byte-escape.toml create mode 100644 src/test/invalid/string-bad-escape.toml create mode 100644 src/test/invalid/string-byte-escapes.toml create mode 100644 src/test/invalid/string-no-close.toml create mode 100644 src/test/invalid/table-array-implicit.toml create mode 100644 src/test/invalid/table-array-malformed-bracket.toml create mode 100644 src/test/invalid/table-array-malformed-empty.toml create mode 100644 src/test/invalid/table-nested-brackets-close.toml create mode 100644 src/test/invalid/table-nested-brackets-open.toml create mode 100644 src/test/invalid/text-after-array-entries.toml create mode 100644 src/test/invalid/text-after-integer.toml create mode 100644 src/test/invalid/text-after-string.toml create mode 100644 src/test/invalid/text-after-table.toml create mode 100644 src/test/invalid/text-before-array-separator.toml create mode 100644 src/test/invalid/text-in-array.toml create mode 100644 src/test/mod.rs create mode 100644 src/test/valid.rs create mode 100644 src/test/valid/array-empty.json create mode 100644 src/test/valid/array-empty.toml create mode 100644 src/test/valid/array-nospaces.json create mode 100644 src/test/valid/array-nospaces.toml create mode 100644 src/test/valid/arrays-hetergeneous.json create mode 100644 src/test/valid/arrays-hetergeneous.toml create mode 100644 src/test/valid/arrays-nested.json create mode 100644 src/test/valid/arrays-nested.toml create mode 100644 src/test/valid/arrays.json create mode 100644 src/test/valid/arrays.toml create mode 100644 src/test/valid/bool.json create mode 100644 src/test/valid/bool.toml create mode 100644 src/test/valid/comments-everywhere.json create mode 100644 src/test/valid/comments-everywhere.toml create mode 100644 src/test/valid/datetime.json create mode 100644 src/test/valid/datetime.toml create mode 100644 src/test/valid/empty.json create mode 100644 src/test/valid/empty.toml create mode 100644 src/test/valid/example.json create mode 100644 src/test/valid/example.toml create mode 100644 src/test/valid/float.json create mode 100644 src/test/valid/float.toml create mode 100644 src/test/valid/implicit-and-explicit-after.json create mode 100644 src/test/valid/implicit-and-explicit-after.toml create mode 100644 src/test/valid/implicit-and-explicit-before.json create mode 100644 src/test/valid/implicit-and-explicit-before.toml create mode 100644 src/test/valid/implicit-groups.json create mode 100644 src/test/valid/implicit-groups.toml create mode 100644 src/test/valid/integer.json create mode 100644 src/test/valid/integer.toml create mode 100644 src/test/valid/key-equals-nospace.json create mode 100644 src/test/valid/key-equals-nospace.toml create mode 100644 src/test/valid/key-special-chars.json create mode 100644 src/test/valid/key-special-chars.toml create mode 100644 src/test/valid/key-with-pound.json create mode 100644 src/test/valid/key-with-pound.toml create mode 100644 src/test/valid/long-float.json create mode 100644 src/test/valid/long-float.toml create mode 100644 src/test/valid/long-integer.json create mode 100644 src/test/valid/long-integer.toml create mode 100644 src/test/valid/string-escapes.json create mode 100644 src/test/valid/string-escapes.toml create mode 100644 src/test/valid/string-simple.json create mode 100644 src/test/valid/string-simple.toml create mode 100644 src/test/valid/string-with-pound.json create mode 100644 src/test/valid/string-with-pound.toml create mode 100644 src/test/valid/table-array-implicit.json create mode 100644 src/test/valid/table-array-implicit.toml create mode 100644 src/test/valid/table-array-many.json create mode 100644 src/test/valid/table-array-many.toml create mode 100644 src/test/valid/table-array-nest.json create mode 100644 src/test/valid/table-array-nest.toml create mode 100644 src/test/valid/table-array-one.json create mode 100644 src/test/valid/table-array-one.toml create mode 100644 src/test/valid/table-empty.json create mode 100644 src/test/valid/table-empty.toml create mode 100644 src/test/valid/table-sub-empty.json create mode 100644 src/test/valid/table-sub-empty.toml create mode 100644 src/test/valid/table-whitespace.json create mode 100644 src/test/valid/table-whitespace.toml create mode 100644 src/test/valid/table-with-pound.json create mode 100644 src/test/valid/table-with-pound.toml create mode 100644 src/test/valid/unicode-escape.json create mode 100644 src/test/valid/unicode-escape.toml create mode 100644 src/test/valid/unicode-literal.json create mode 100644 src/test/valid/unicode-literal.toml create mode 100644 src/toml.rs diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000..dac3e28 --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,617 @@ +use std::char; +use std::collections::{HashMap, HashSet}; +use std::num::FromStrRadix; +use std::str; + +use {Array, Table, Value, String, Float, Integer, Boolean, Datetime}; + +pub struct Parser<'a> { + input: &'a str, + cur: str::CharOffsets<'a>, + tables_defined: HashSet, + pub errors: Vec, +} + +#[deriving(Show)] +pub struct Error { + pub lo: uint, + pub hi: uint, + pub desc: String, +} + +impl<'a> Parser<'a> { + pub fn new(s: &'a str) -> Parser<'a> { + Parser { + input: s, + cur: s.char_indices(), + errors: Vec::new(), + tables_defined: HashSet::new(), + } + } + + fn next_pos(&self) -> uint { + self.cur.clone().next().map(|p| p.val0()).unwrap_or(self.input.len()) + } + + fn eat(&mut self, ch: char) -> bool { + match self.cur.clone().next() { + Some((_, c)) if c == ch => { self.cur.next(); true } + Some(_) | None => false, + } + } + + fn expect(&mut self, ch: char) -> bool { + if self.eat(ch) { return true } + let mut it = self.cur.clone(); + let lo = it.next().map(|p| p.val0()).unwrap_or(self.input.len()); + let hi = it.next().map(|p| p.val0()).unwrap_or(self.input.len()); + self.errors.push(Error { + lo: lo, + hi: hi, + desc: match self.cur.clone().next() { + Some((_, c)) => format!("expected `{}`, but found `{}`", ch, c), + None => format!("expected `{}`, but found eof", ch) + } + }); + false + } + + fn ws(&mut self) { + loop { + match self.cur.clone().next() { + Some((_, '\t')) | + Some((_, ' ')) => { self.cur.next(); } + _ => break, + } + } + } + + fn comment(&mut self) { + match self.cur.clone().next() { + Some((_, '#')) => {} + _ => return, + } + for (_, ch) in self.cur { + if ch == '\n' { break } + } + } + + pub fn parse(&mut self) -> Option { + let mut ret = HashMap::new(); + loop { + self.ws(); + match self.cur.clone().next() { + Some((_, '#')) => { self.comment(); } + Some((_, '\n')) => { self.cur.next(); } + Some((start, '[')) => { + self.cur.next(); + let array = self.eat('['); + let mut section = String::new(); + for (pos, ch) in self.cur { + if ch == ']' { break } + if ch == '[' { + self.errors.push(Error { + lo: pos, + hi: pos + 1, + desc: format!("section names cannot contain \ + a `[` character"), + }); + continue + } + section.push_char(ch); + } + + if section.len() == 0 { + self.errors.push(Error { + lo: start, + hi: start + if array {3} else {1}, + desc: format!("section name must not be empty"), + }); + continue + } else if array && !self.expect(']') { + return None + } + + let mut table = HashMap::new(); + if !self.values(&mut table) { return None } + if array { + self.insert_array(&mut ret, section, Table(table), start) + } else { + self.insert_table(&mut ret, section, table, start) + } + } + Some(_) => { + if !self.values(&mut ret) { return None } + } + None if self.errors.len() == 0 => return Some(ret), + None => return None, + } + } + } + + fn values(&mut self, into: &mut Table) -> bool { + loop { + self.ws(); + match self.cur.clone().next() { + Some((_, '#')) => self.comment(), + Some((_, '\n')) => { self.cur.next(); } + Some((_, '[')) => break, + Some((start, _)) => { + let mut key = String::new(); + let mut found_eq = false; + for (pos, ch) in self.cur { + match ch { + ' ' | '\t' => break, + '=' => { found_eq = true; break } + '\n' => { + self.errors.push(Error { + lo: start, + hi: pos + 1, + desc: format!("keys cannot be defined \ + across lines"), + }) + } + c => key.push_char(c), + } + } + if !found_eq { + self.ws(); + if !self.expect('=') { return false } + } + + let value = match self.value() { + Some(value) => value, + None => return false, + }; + self.insert(into, key, value, start); + self.ws(); + self.comment(); + self.eat('\n'); + } + None => break, + } + } + return true + } + + fn value(&mut self) -> Option { + self.ws(); + match self.cur.clone().next() { + Some((pos, '"')) => self.string(pos), + Some((pos, 't')) | + Some((pos, 'f')) => self.boolean(pos), + Some((pos, '[')) => self.array(pos), + Some((pos, '-')) => self.number_or_datetime(pos), + Some((pos, ch)) if ch.is_digit() => self.number_or_datetime(pos), + _ => { + let mut it = self.cur.clone(); + let lo = it.next().map(|p| p.val0()).unwrap_or(self.input.len()); + let hi = it.next().map(|p| p.val0()).unwrap_or(self.input.len()); + self.errors.push(Error { + lo: lo, + hi: hi, + desc: format!("expected a value"), + }); + return None + } + } + } + + fn string(&mut self, start: uint) -> Option { + if !self.expect('"') { return None } + let mut ret = String::new(); + + loop { + match self.cur.next() { + Some((_, '"')) => break, + Some((pos, '\\')) => { + match escape(self, pos) { + Some(c) => ret.push_char(c), + None => {} + } + } + Some((pos, ch)) if ch < '\u001f' => { + let mut escaped = String::new(); + ch.escape_default(|c| escaped.push_char(c)); + self.errors.push(Error { + lo: pos, + hi: pos + 1, + desc: format!("control character `{}` must be escaped", + escaped) + }); + } + Some((_, ch)) => ret.push_char(ch), + None => { + self.errors.push(Error { + lo: start, + hi: self.input.len(), + desc: format!("unterminated string literal"), + }); + return None + } + } + } + + return Some(String(ret)); + + fn escape(me: &mut Parser, pos: uint) -> Option { + match me.cur.next() { + Some((_, 'b')) => Some('\u0008'), + Some((_, 't')) => Some('\u0009'), + Some((_, 'n')) => Some('\u000a'), + Some((_, 'f')) => Some('\u000c'), + Some((_, 'r')) => Some('\u000d'), + Some((_, '"')) => Some('\u0022'), + Some((_, '/')) => Some('\u002f'), + Some((_, '\\')) => Some('\u005c'), + Some((pos, 'u')) => { + let num = if me.input.is_char_boundary(pos + 5) { + me.input.slice(pos + 1, pos + 5) + } else { + "invalid" + }; + match FromStrRadix::from_str_radix(num, 16) { + Some(n) => { + match char::from_u32(n) { + Some(c) => { + me.cur.next(); + me.cur.next(); + me.cur.next(); + me.cur.next(); + return Some(c) + } + None => { + me.errors.push(Error { + lo: pos + 1, + hi: pos + 5, + desc: format!("codepoint `{:x}` is \ + not a valid unicode \ + codepoint", n), + }) + } + } + } + None => { + me.errors.push(Error { + lo: pos, + hi: pos + 1, + desc: format!("expected four hex digits \ + after a `u` escape"), + }) + } + } + None + } + Some((pos, ch)) => { + let mut escaped = String::new(); + ch.escape_default(|c| escaped.push_char(c)); + let next_pos = me.next_pos(); + me.errors.push(Error { + lo: pos, + hi: next_pos, + desc: format!("unknown string escape: `{}`", + escaped), + }); + None + } + None => { + me.errors.push(Error { + lo: pos, + hi: pos + 1, + desc: format!("unterminated escape sequence"), + }); + None + } + } + } + } + + fn number_or_datetime(&mut self, start: uint) -> Option { + let negative = self.eat('-'); + let mut is_float = false; + loop { + match self.cur.clone().next() { + Some((_, ch)) if ch.is_digit() => { self.cur.next(); } + Some((_, '.')) if !is_float => { + is_float = true; + self.cur.next(); + } + Some(_) | None => break, + } + } + let end = self.next_pos(); + let ret = if is_float { + if self.input.char_at_reverse(end) == '.' { + None + } else { + from_str::(self.input.slice(start, end)).map(Float) + } + } else if !negative && self.eat('-') { + self.datetime(start, end + 1) + } else { + from_str::(self.input.slice(start, end)).map(Integer) + }; + if ret.is_none() { + self.errors.push(Error { + lo: start, + hi: end, + desc: format!("invalid numeric literal"), + }); + } + return ret; + } + + fn boolean(&mut self, start: uint) -> Option { + let rest = self.input.slice_from(start); + if rest.starts_with("true") { + for _ in range(0, 4) { + self.cur.next(); + } + Some(Boolean(true)) + } else if rest.starts_with("false") { + for _ in range(0, 5) { + self.cur.next(); + } + Some(Boolean(false)) + } else { + let next = self.next_pos(); + self.errors.push(Error { + lo: start, + hi: next, + desc: format!("unexpected character: `{}`", + rest.char_at(0)), + }); + None + } + } + + fn datetime(&mut self, start: uint, end_so_far: uint) -> Option { + let mut date = self.input.slice(start, end_so_far).to_string(); + for _ in range(0, 15) { + match self.cur.next() { + Some((_, ch)) => date.push_char(ch), + None => { + self.errors.push(Error { + lo: start, + hi: end_so_far, + desc: format!("malformed date literal"), + }); + return None + } + } + } + let mut it = date.as_slice().chars(); + let mut valid = true; + valid = valid && it.next().map(|c| c.is_digit()).unwrap_or(false); + valid = valid && it.next().map(|c| c.is_digit()).unwrap_or(false); + valid = valid && it.next().map(|c| c.is_digit()).unwrap_or(false); + valid = valid && it.next().map(|c| c.is_digit()).unwrap_or(false); + valid = valid && it.next().map(|c| c == '-').unwrap_or(false); + valid = valid && it.next().map(|c| c.is_digit()).unwrap_or(false); + valid = valid && it.next().map(|c| c.is_digit()).unwrap_or(false); + valid = valid && it.next().map(|c| c == '-').unwrap_or(false); + valid = valid && it.next().map(|c| c.is_digit()).unwrap_or(false); + valid = valid && it.next().map(|c| c.is_digit()).unwrap_or(false); + valid = valid && it.next().map(|c| c == 'T').unwrap_or(false); + valid = valid && it.next().map(|c| c.is_digit()).unwrap_or(false); + valid = valid && it.next().map(|c| c.is_digit()).unwrap_or(false); + valid = valid && it.next().map(|c| c == ':').unwrap_or(false); + valid = valid && it.next().map(|c| c.is_digit()).unwrap_or(false); + valid = valid && it.next().map(|c| c.is_digit()).unwrap_or(false); + valid = valid && it.next().map(|c| c == ':').unwrap_or(false); + valid = valid && it.next().map(|c| c.is_digit()).unwrap_or(false); + valid = valid && it.next().map(|c| c.is_digit()).unwrap_or(false); + valid = valid && it.next().map(|c| c == 'Z').unwrap_or(false); + if valid { + Some(Datetime(date.clone())) + } else { + self.errors.push(Error { + lo: start, + hi: start + date.len(), + desc: format!("malformed date literal"), + }); + None + } + } + + fn array(&mut self, _start: uint) -> Option { + if !self.expect('[') { return None } + let mut ret = Vec::new(); + fn consume(me: &mut Parser) { + loop { + me.ws(); + match me.cur.clone().next() { + Some((_, '#')) => { me.comment(); } + Some((_, '\n')) => { me.cur.next(); } + _ => break, + } + } + } + let mut type_str = None; + loop { + // Break out early if we see the closing bracket + consume(self); + if self.eat(']') { return Some(Array(ret)) } + + // Attempt to parse a value, triggering an error if it's the wrong + // type. + let start = self.next_pos(); + let value = match self.value() { + Some(v) => v, + None => return None, + }; + let end = self.next_pos(); + let expected = type_str.unwrap_or(value.type_str()); + if value.type_str() != expected { + self.errors.push(Error { + lo: start, + hi: end, + desc: format!("expected type `{}`, found type `{}`", + expected, value.type_str()), + }); + } else { + type_str = Some(expected); + ret.push(value); + } + + // Look for a comma. If we don't find one we're done + consume(self); + if !self.eat(',') { break } + } + consume(self); + if !self.expect(']') { return None } + return Some(Array(ret)) + } + + fn insert(&mut self, into: &mut Table, key: String, value: Value, + key_lo: uint) { + if into.contains_key(&key) { + self.errors.push(Error { + lo: key_lo, + hi: key_lo + key.len(), + desc: format!("duplicate key: `{}`", key), + }) + } else { + into.insert(key, value); + } + } + + fn recurse<'a>(&mut self, mut cur: &'a mut Table, orig_key: &'a str, + key_lo: uint) -> Option<(&'a mut Table, &'a str)> { + if orig_key.starts_with(".") || orig_key.ends_with(".") || + orig_key.contains("..") { + self.errors.push(Error { + lo: key_lo, + hi: key_lo + orig_key.len(), + desc: format!("tables cannot have empty names"), + }); + return None + } + let key = match orig_key.rfind('.') { + Some(n) => orig_key.slice_to(n), + None => return Some((cur, orig_key)), + }; + for part in key.as_slice().split('.') { + let part = part.to_string(); + let tmp = cur; + + if tmp.contains_key(&part) { + match *tmp.get_mut(&part) { + Table(ref mut table) => { + cur = table; + continue + } + Array(ref mut array) => { + match array.as_mut_slice().mut_last() { + Some(&Table(ref mut table)) => cur = table, + _ => { + self.errors.push(Error { + lo: key_lo, + hi: key_lo + key.len(), + desc: format!("array `{}` does not contain \ + tables", part) + }); + return None + } + } + continue + } + _ => { + self.errors.push(Error { + lo: key_lo, + hi: key_lo + key.len(), + desc: format!("key `{}` was not previously a table", + part) + }); + return None + } + } + } + + // Initialize an empty table as part of this sub-key + tmp.insert(part.clone(), Table(HashMap::new())); + match *tmp.get_mut(&part) { + Table(ref mut inner) => cur = inner, + _ => unreachable!(), + } + } + return Some((cur, orig_key.slice_from(key.len() + 1))) + } + + fn insert_table(&mut self, into: &mut Table, key: String, value: Table, + key_lo: uint) { + if !self.tables_defined.insert(key.clone()) { + self.errors.push(Error { + lo: key_lo, + hi: key_lo + key.len(), + desc: format!("redefinition of table `{}`", key), + }); + return + } + + let (into, key) = match self.recurse(into, key.as_slice(), key_lo) { + Some(pair) => pair, + None => return, + }; + let key = key.to_string(); + if !into.contains_key(&key) { + into.insert(key.clone(), Table(HashMap::new())); + } + match into.find_mut(&key) { + Some(&Table(ref mut table)) => { + for (k, v) in value.move_iter() { + if !table.insert(k.clone(), v) { + self.errors.push(Error { + lo: key_lo, + hi: key_lo + key.len(), + desc: format!("duplicate key `{}` in table", k), + }); + } + } + } + Some(_) => { + self.errors.push(Error { + lo: key_lo, + hi: key_lo + key.len(), + desc: format!("duplicate key `{}` in table", key), + }); + } + None => {} + } + } + + fn insert_array(&mut self, into: &mut Table, key: String, value: Value, + key_lo: uint) { + let (into, key) = match self.recurse(into, key.as_slice(), key_lo) { + Some(pair) => pair, + None => return, + }; + let key = key.to_string(); + if !into.contains_key(&key) { + into.insert(key.clone(), Array(Vec::new())); + } + match *into.get_mut(&key) { + Array(ref mut vec) => { + match vec.as_slice().head() { + Some(ref v) if !v.same_type(&value) => { + self.errors.push(Error { + lo: key_lo, + hi: key_lo + key.len(), + desc: format!("expected type `{}`, found type `{}`", + v.type_str(), value.type_str()), + }) + } + Some(..) | None => {} + } + vec.push(value); + } + _ => { + self.errors.push(Error { + lo: key_lo, + hi: key_lo + key.len(), + desc: format!("key `{}` was previously not an array", key), + }); + } + } + } +} diff --git a/src/test/README.md b/src/test/README.md new file mode 100644 index 0000000..ebbc01c --- /dev/null +++ b/src/test/README.md @@ -0,0 +1 @@ +Tests are from https://github.com/BurntSushi/toml-test diff --git a/src/test/invalid-encoder/array-mixed-types-ints-and-floats.json b/src/test/invalid-encoder/array-mixed-types-ints-and-floats.json new file mode 100644 index 0000000..b7920a0 --- /dev/null +++ b/src/test/invalid-encoder/array-mixed-types-ints-and-floats.json @@ -0,0 +1,15 @@ +{ + "ints-and-floats": { + "type": "array", + "value": [ + { + "type": "integer", + "value": "1" + }, + { + "type": "float", + "value": "1.0" + } + ] + } +} diff --git a/src/test/invalid.rs b/src/test/invalid.rs new file mode 100644 index 0000000..9f86d13 --- /dev/null +++ b/src/test/invalid.rs @@ -0,0 +1,76 @@ +use {Parser}; + +fn run(toml: &str) { + let mut p = Parser::new(toml); + let table = p.parse(); + assert!(p.errors.len() > 0); + assert!(table.is_none()); +} + +macro_rules! test( ($name:ident, $toml:expr) => ( + #[test] + fn $name() { run($toml); } +) ) + +test!(array_mixed_types_arrays_and_ints, + include_str!("invalid/array-mixed-types-arrays-and-ints.toml")) +test!(array_mixed_types_ints_and_floats, + include_str!("invalid/array-mixed-types-ints-and-floats.toml")) +test!(array_mixed_types_strings_and_ints, + include_str!("invalid/array-mixed-types-strings-and-ints.toml")) +test!(datetime_malformed_no_leads, + include_str!("invalid/datetime-malformed-no-leads.toml")) +test!(datetime_malformed_no_secs, + include_str!("invalid/datetime-malformed-no-secs.toml")) +test!(datetime_malformed_no_t, + include_str!("invalid/datetime-malformed-no-t.toml")) +test!(datetime_malformed_no_z, + include_str!("invalid/datetime-malformed-no-z.toml")) +test!(datetime_malformed_with_milli, + include_str!("invalid/datetime-malformed-with-milli.toml")) +test!(duplicate_keys, + include_str!("invalid/duplicate-keys.toml")) +test!(duplicate_key_table, + include_str!("invalid/duplicate-key-table.toml")) +test!(duplicate_tables, + include_str!("invalid/duplicate-tables.toml")) +test!(empty_implicit_table, + include_str!("invalid/empty-implicit-table.toml")) +test!(empty_table, + include_str!("invalid/empty-table.toml")) +test!(float_no_leading_zero, + include_str!("invalid/float-no-leading-zero.toml")) +test!(float_no_trailing_digits, + include_str!("invalid/float-no-trailing-digits.toml")) +test!(key_two_equals, + include_str!("invalid/key-two-equals.toml")) +test!(string_bad_byte_escape, + include_str!("invalid/string-bad-byte-escape.toml")) +test!(string_bad_escape, + include_str!("invalid/string-bad-escape.toml")) +test!(string_byte_escapes, + include_str!("invalid/string-byte-escapes.toml")) +test!(string_no_close, + include_str!("invalid/string-no-close.toml")) +test!(table_array_implicit, + include_str!("invalid/table-array-implicit.toml")) +test!(table_array_malformed_bracket, + include_str!("invalid/table-array-malformed-bracket.toml")) +test!(table_array_malformed_empty, + include_str!("invalid/table-array-malformed-empty.toml")) +test!(table_nested_brackets_close, + include_str!("invalid/table-nested-brackets-close.toml")) +test!(table_nested_brackets_open, + include_str!("invalid/table-nested-brackets-open.toml")) +test!(text_after_array_entries, + include_str!("invalid/text-after-array-entries.toml")) +test!(text_after_integer, + include_str!("invalid/text-after-integer.toml")) +test!(text_after_string, + include_str!("invalid/text-after-string.toml")) +test!(text_after_table, + include_str!("invalid/text-after-table.toml")) +test!(text_before_array_separator, + include_str!("invalid/text-before-array-separator.toml")) +test!(text_in_array, + include_str!("invalid/text-in-array.toml")) diff --git a/src/test/invalid/array-mixed-types-arrays-and-ints.toml b/src/test/invalid/array-mixed-types-arrays-and-ints.toml new file mode 100644 index 0000000..051ec73 --- /dev/null +++ b/src/test/invalid/array-mixed-types-arrays-and-ints.toml @@ -0,0 +1 @@ +arrays-and-ints = [1, ["Arrays are not integers."]] diff --git a/src/test/invalid/array-mixed-types-ints-and-floats.toml b/src/test/invalid/array-mixed-types-ints-and-floats.toml new file mode 100644 index 0000000..51ebe80 --- /dev/null +++ b/src/test/invalid/array-mixed-types-ints-and-floats.toml @@ -0,0 +1 @@ +ints-and-floats = [1, 1.0] diff --git a/src/test/invalid/array-mixed-types-strings-and-ints.toml b/src/test/invalid/array-mixed-types-strings-and-ints.toml new file mode 100644 index 0000000..f348308 --- /dev/null +++ b/src/test/invalid/array-mixed-types-strings-and-ints.toml @@ -0,0 +1 @@ +strings-and-ints = ["hi", 42] diff --git a/src/test/invalid/datetime-malformed-no-leads.toml b/src/test/invalid/datetime-malformed-no-leads.toml new file mode 100644 index 0000000..123f173 --- /dev/null +++ b/src/test/invalid/datetime-malformed-no-leads.toml @@ -0,0 +1 @@ +no-leads = 1987-7-05T17:45:00Z diff --git a/src/test/invalid/datetime-malformed-no-secs.toml b/src/test/invalid/datetime-malformed-no-secs.toml new file mode 100644 index 0000000..ba93900 --- /dev/null +++ b/src/test/invalid/datetime-malformed-no-secs.toml @@ -0,0 +1 @@ +no-secs = 1987-07-05T17:45Z diff --git a/src/test/invalid/datetime-malformed-no-t.toml b/src/test/invalid/datetime-malformed-no-t.toml new file mode 100644 index 0000000..617e3c5 --- /dev/null +++ b/src/test/invalid/datetime-malformed-no-t.toml @@ -0,0 +1 @@ +no-t = 1987-07-0517:45:00Z diff --git a/src/test/invalid/datetime-malformed-no-z.toml b/src/test/invalid/datetime-malformed-no-z.toml new file mode 100644 index 0000000..cf66b1e --- /dev/null +++ b/src/test/invalid/datetime-malformed-no-z.toml @@ -0,0 +1 @@ +no-z = 1987-07-05T17:45:00 diff --git a/src/test/invalid/datetime-malformed-with-milli.toml b/src/test/invalid/datetime-malformed-with-milli.toml new file mode 100644 index 0000000..eef792f --- /dev/null +++ b/src/test/invalid/datetime-malformed-with-milli.toml @@ -0,0 +1 @@ +with-milli = 1987-07-5T17:45:00.12Z diff --git a/src/test/invalid/duplicate-key-table.toml b/src/test/invalid/duplicate-key-table.toml new file mode 100644 index 0000000..cedf05f --- /dev/null +++ b/src/test/invalid/duplicate-key-table.toml @@ -0,0 +1,5 @@ +[fruit] +type = "apple" + +[fruit.type] +apple = "yes" diff --git a/src/test/invalid/duplicate-keys.toml b/src/test/invalid/duplicate-keys.toml new file mode 100644 index 0000000..9b5aee0 --- /dev/null +++ b/src/test/invalid/duplicate-keys.toml @@ -0,0 +1,2 @@ +dupe = false +dupe = true diff --git a/src/test/invalid/duplicate-tables.toml b/src/test/invalid/duplicate-tables.toml new file mode 100644 index 0000000..8ddf49b --- /dev/null +++ b/src/test/invalid/duplicate-tables.toml @@ -0,0 +1,2 @@ +[a] +[a] diff --git a/src/test/invalid/empty-implicit-table.toml b/src/test/invalid/empty-implicit-table.toml new file mode 100644 index 0000000..0cc36d0 --- /dev/null +++ b/src/test/invalid/empty-implicit-table.toml @@ -0,0 +1 @@ +[naughty..naughty] diff --git a/src/test/invalid/empty-table.toml b/src/test/invalid/empty-table.toml new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/src/test/invalid/empty-table.toml @@ -0,0 +1 @@ +[] diff --git a/src/test/invalid/float-no-leading-zero.toml b/src/test/invalid/float-no-leading-zero.toml new file mode 100644 index 0000000..cab76bf --- /dev/null +++ b/src/test/invalid/float-no-leading-zero.toml @@ -0,0 +1,2 @@ +answer = .12345 +neganswer = -.12345 diff --git a/src/test/invalid/float-no-trailing-digits.toml b/src/test/invalid/float-no-trailing-digits.toml new file mode 100644 index 0000000..cbff2d0 --- /dev/null +++ b/src/test/invalid/float-no-trailing-digits.toml @@ -0,0 +1,2 @@ +answer = 1. +neganswer = -1. diff --git a/src/test/invalid/key-two-equals.toml b/src/test/invalid/key-two-equals.toml new file mode 100644 index 0000000..25a0378 --- /dev/null +++ b/src/test/invalid/key-two-equals.toml @@ -0,0 +1 @@ +key= = 1 diff --git a/src/test/invalid/string-bad-byte-escape.toml b/src/test/invalid/string-bad-byte-escape.toml new file mode 100644 index 0000000..4c7be59 --- /dev/null +++ b/src/test/invalid/string-bad-byte-escape.toml @@ -0,0 +1 @@ +naughty = "\xAg" diff --git a/src/test/invalid/string-bad-escape.toml b/src/test/invalid/string-bad-escape.toml new file mode 100644 index 0000000..60acb0c --- /dev/null +++ b/src/test/invalid/string-bad-escape.toml @@ -0,0 +1 @@ +invalid-escape = "This string has a bad \a escape character." diff --git a/src/test/invalid/string-byte-escapes.toml b/src/test/invalid/string-byte-escapes.toml new file mode 100644 index 0000000..e94452a --- /dev/null +++ b/src/test/invalid/string-byte-escapes.toml @@ -0,0 +1 @@ +answer = "\x33" diff --git a/src/test/invalid/string-no-close.toml b/src/test/invalid/string-no-close.toml new file mode 100644 index 0000000..0c292fc --- /dev/null +++ b/src/test/invalid/string-no-close.toml @@ -0,0 +1 @@ +no-ending-quote = "One time, at band camp diff --git a/src/test/invalid/table-array-implicit.toml b/src/test/invalid/table-array-implicit.toml new file mode 100644 index 0000000..05f2507 --- /dev/null +++ b/src/test/invalid/table-array-implicit.toml @@ -0,0 +1,14 @@ +# This test is a bit tricky. It should fail because the first use of +# `[[albums.songs]]` without first declaring `albums` implies that `albums` +# must be a table. The alternative would be quite weird. Namely, it wouldn't +# comply with the TOML spec: "Each double-bracketed sub-table will belong to +# the most *recently* defined table element *above* it." +# +# This is in contrast to the *valid* test, table-array-implicit where +# `[[albums.songs]]` works by itself, so long as `[[albums]]` isn't declared +# later. (Although, `[albums]` could be.) +[[albums.songs]] +name = "Glory Days" + +[[albums]] +name = "Born in the USA" diff --git a/src/test/invalid/table-array-malformed-bracket.toml b/src/test/invalid/table-array-malformed-bracket.toml new file mode 100644 index 0000000..39c73b0 --- /dev/null +++ b/src/test/invalid/table-array-malformed-bracket.toml @@ -0,0 +1,2 @@ +[[albums] +name = "Born to Run" diff --git a/src/test/invalid/table-array-malformed-empty.toml b/src/test/invalid/table-array-malformed-empty.toml new file mode 100644 index 0000000..a470ca3 --- /dev/null +++ b/src/test/invalid/table-array-malformed-empty.toml @@ -0,0 +1,2 @@ +[[]] +name = "Born to Run" diff --git a/src/test/invalid/table-nested-brackets-close.toml b/src/test/invalid/table-nested-brackets-close.toml new file mode 100644 index 0000000..c8b5a67 --- /dev/null +++ b/src/test/invalid/table-nested-brackets-close.toml @@ -0,0 +1,2 @@ +[a]b] +zyx = 42 diff --git a/src/test/invalid/table-nested-brackets-open.toml b/src/test/invalid/table-nested-brackets-open.toml new file mode 100644 index 0000000..246d7e9 --- /dev/null +++ b/src/test/invalid/table-nested-brackets-open.toml @@ -0,0 +1,2 @@ +[a[b] +zyx = 42 diff --git a/src/test/invalid/text-after-array-entries.toml b/src/test/invalid/text-after-array-entries.toml new file mode 100644 index 0000000..1a72890 --- /dev/null +++ b/src/test/invalid/text-after-array-entries.toml @@ -0,0 +1,4 @@ +array = [ + "Is there life after an array separator?", No + "Entry" +] diff --git a/src/test/invalid/text-after-integer.toml b/src/test/invalid/text-after-integer.toml new file mode 100644 index 0000000..42de7af --- /dev/null +++ b/src/test/invalid/text-after-integer.toml @@ -0,0 +1 @@ +answer = 42 the ultimate answer? diff --git a/src/test/invalid/text-after-string.toml b/src/test/invalid/text-after-string.toml new file mode 100644 index 0000000..c92a6f1 --- /dev/null +++ b/src/test/invalid/text-after-string.toml @@ -0,0 +1 @@ +string = "Is there life after strings?" No. diff --git a/src/test/invalid/text-after-table.toml b/src/test/invalid/text-after-table.toml new file mode 100644 index 0000000..87da9db --- /dev/null +++ b/src/test/invalid/text-after-table.toml @@ -0,0 +1 @@ +[error] this shouldn't be here diff --git a/src/test/invalid/text-before-array-separator.toml b/src/test/invalid/text-before-array-separator.toml new file mode 100644 index 0000000..9b06a39 --- /dev/null +++ b/src/test/invalid/text-before-array-separator.toml @@ -0,0 +1,4 @@ +array = [ + "Is there life before an array separator?" No, + "Entry" +] diff --git a/src/test/invalid/text-in-array.toml b/src/test/invalid/text-in-array.toml new file mode 100644 index 0000000..a6a6c42 --- /dev/null +++ b/src/test/invalid/text-in-array.toml @@ -0,0 +1,5 @@ +array = [ + "Entry 1", + I don't belong, + "Entry 2", +] diff --git a/src/test/mod.rs b/src/test/mod.rs new file mode 100644 index 0000000..6f2c7bd --- /dev/null +++ b/src/test/mod.rs @@ -0,0 +1,2 @@ +mod valid; +mod invalid; diff --git a/src/test/valid.rs b/src/test/valid.rs new file mode 100644 index 0000000..335e908 --- /dev/null +++ b/src/test/valid.rs @@ -0,0 +1,163 @@ +extern crate serialize; + +use std::num::strconv; +use std::collections::TreeMap; +use self::serialize::json; + +use {Parser, Value, Table, String, Integer, Float, Boolean, Datetime, Array}; + +fn to_json(toml: Value) -> json::Json { + fn doit(s: &str, json: json::Json) -> json::Json { + let mut map = box TreeMap::new(); + map.insert("type".to_string(), json::String(s.to_string())); + map.insert("value".to_string(), json); + json::Object(map) + } + match toml { + String(s) => doit("string", json::String(s)), + Integer(i) => doit("integer", json::String(i.to_str())), + Float(f) => doit("float", json::String({ + let (bytes, _) = + strconv::float_to_str_bytes_common(f, 10, true, + strconv::SignNeg, + strconv::DigMax(15), + strconv::ExpNone, + false); + let s = String::from_utf8(bytes).unwrap(); + if s.as_slice().contains(".") {s} else {format!("{}.0", s)} + })), + Boolean(b) => doit("bool", json::String(b.to_str())), + Datetime(s) => doit("datetime", json::String(s)), + Array(arr) => { + let is_table = match arr.as_slice().head() { + Some(&Table(..)) => true, + _ => false, + }; + let json = json::List(arr.move_iter().map(to_json).collect()); + if is_table {json} else {doit("array", json)} + } + Table(table) => json::Object(box table.move_iter().map(|(k, v)| { + (k, to_json(v)) + }).collect()), + } +} + +fn run(toml: &str, json: &str) { + let mut p = Parser::new(toml); + let table = p.parse(); + assert!(p.errors.len() == 0, "had_errors: {}", + p.errors.iter().map(|e| { + (e.desc.clone(), toml.slice(e.lo - 5, e.hi + 5)) + }).collect::>()); + assert!(table.is_some()); + let table = table.unwrap(); + + let json = json::from_str(json).unwrap(); + let toml_json = to_json(Table(table)); + assert!(json == toml_json, + "expected\n{}\ngot\n{}\n", + json.to_pretty_str(), + toml_json.to_pretty_str()); +} + +macro_rules! test( ($name:ident, $toml:expr, $json:expr) => ( + #[test] + fn $name() { run($toml, $json); } +) ) + +test!(array_empty, + include_str!("valid/array-empty.toml"), + include_str!("valid/array-empty.json")) +test!(array_nospaces, + include_str!("valid/array-nospaces.toml"), + include_str!("valid/array-nospaces.json")) +test!(arrays_hetergeneous, + include_str!("valid/arrays-hetergeneous.toml"), + include_str!("valid/arrays-hetergeneous.json")) +test!(arrays, + include_str!("valid/arrays.toml"), + include_str!("valid/arrays.json")) +test!(arrays_nested, + include_str!("valid/arrays-nested.toml"), + include_str!("valid/arrays-nested.json")) +test!(empty, + include_str!("valid/empty.toml"), + include_str!("valid/empty.json")) +test!(bool, + include_str!("valid/bool.toml"), + include_str!("valid/bool.json")) +test!(datetime, + include_str!("valid/datetime.toml"), + include_str!("valid/datetime.json")) +test!(example, + include_str!("valid/example.toml"), + include_str!("valid/example.json")) +test!(float, + include_str!("valid/float.toml"), + include_str!("valid/float.json")) +test!(implicit_and_explicit_after, + include_str!("valid/implicit-and-explicit-after.toml"), + include_str!("valid/implicit-and-explicit-after.json")) +test!(implicit_and_explicit_before, + include_str!("valid/implicit-and-explicit-before.toml"), + include_str!("valid/implicit-and-explicit-before.json")) +test!(implicit_groups, + include_str!("valid/implicit-groups.toml"), + include_str!("valid/implicit-groups.json")) +test!(integer, + include_str!("valid/integer.toml"), + include_str!("valid/integer.json")) +test!(key_equals_nospace, + include_str!("valid/key-equals-nospace.toml"), + include_str!("valid/key-equals-nospace.json")) +test!(key_special_chars, + include_str!("valid/key-special-chars.toml"), + include_str!("valid/key-special-chars.json")) +test!(key_with_pound, + include_str!("valid/key-with-pound.toml"), + include_str!("valid/key-with-pound.json")) +test!(long_float, + include_str!("valid/long-float.toml"), + include_str!("valid/long-float.json")) +test!(long_integer, + include_str!("valid/long-integer.toml"), + include_str!("valid/long-integer.json")) +test!(string_escapes, + include_str!("valid/string-escapes.toml"), + include_str!("valid/string-escapes.json")) +test!(string_simple, + include_str!("valid/string-simple.toml"), + include_str!("valid/string-simple.json")) +test!(string_with_pound, + include_str!("valid/string-with-pound.toml"), + include_str!("valid/string-with-pound.json")) +test!(table_array_implicit, + include_str!("valid/table-array-implicit.toml"), + include_str!("valid/table-array-implicit.json")) +test!(table_array_many, + include_str!("valid/table-array-many.toml"), + include_str!("valid/table-array-many.json")) +test!(table_array_nest, + include_str!("valid/table-array-nest.toml"), + include_str!("valid/table-array-nest.json")) +test!(table_array_one, + include_str!("valid/table-array-one.toml"), + include_str!("valid/table-array-one.json")) +test!(table_empty, + include_str!("valid/table-empty.toml"), + include_str!("valid/table-empty.json")) +test!(table_sub_empty, + include_str!("valid/table-sub-empty.toml"), + include_str!("valid/table-sub-empty.json")) +test!(table_whitespace, + include_str!("valid/table-whitespace.toml"), + include_str!("valid/table-whitespace.json")) +test!(table_with_pound, + include_str!("valid/table-with-pound.toml"), + include_str!("valid/table-with-pound.json")) +test!(unicode_escape, + include_str!("valid/unicode-escape.toml"), + include_str!("valid/unicode-escape.json")) +test!(unicode_literal, + include_str!("valid/unicode-literal.toml"), + include_str!("valid/unicode-literal.json")) diff --git a/src/test/valid/array-empty.json b/src/test/valid/array-empty.json new file mode 100644 index 0000000..2fbf256 --- /dev/null +++ b/src/test/valid/array-empty.json @@ -0,0 +1,11 @@ +{ + "thevoid": { "type": "array", "value": [ + {"type": "array", "value": [ + {"type": "array", "value": [ + {"type": "array", "value": [ + {"type": "array", "value": []} + ]} + ]} + ]} + ]} +} diff --git a/src/test/valid/array-empty.toml b/src/test/valid/array-empty.toml new file mode 100644 index 0000000..fa58dc6 --- /dev/null +++ b/src/test/valid/array-empty.toml @@ -0,0 +1 @@ +thevoid = [[[[[]]]]] diff --git a/src/test/valid/array-nospaces.json b/src/test/valid/array-nospaces.json new file mode 100644 index 0000000..1833d61 --- /dev/null +++ b/src/test/valid/array-nospaces.json @@ -0,0 +1,10 @@ +{ + "ints": { + "type": "array", + "value": [ + {"type": "integer", "value": "1"}, + {"type": "integer", "value": "2"}, + {"type": "integer", "value": "3"} + ] + } +} diff --git a/src/test/valid/array-nospaces.toml b/src/test/valid/array-nospaces.toml new file mode 100644 index 0000000..6618936 --- /dev/null +++ b/src/test/valid/array-nospaces.toml @@ -0,0 +1 @@ +ints = [1,2,3] diff --git a/src/test/valid/arrays-hetergeneous.json b/src/test/valid/arrays-hetergeneous.json new file mode 100644 index 0000000..e703739 --- /dev/null +++ b/src/test/valid/arrays-hetergeneous.json @@ -0,0 +1,19 @@ +{ + "mixed": { + "type": "array", + "value": [ + {"type": "array", "value": [ + {"type": "integer", "value": "1"}, + {"type": "integer", "value": "2"} + ]}, + {"type": "array", "value": [ + {"type": "string", "value": "a"}, + {"type": "string", "value": "b"} + ]}, + {"type": "array", "value": [ + {"type": "float", "value": "1.0"}, + {"type": "float", "value": "2.0"} + ]} + ] + } +} diff --git a/src/test/valid/arrays-hetergeneous.toml b/src/test/valid/arrays-hetergeneous.toml new file mode 100644 index 0000000..91fcbdf --- /dev/null +++ b/src/test/valid/arrays-hetergeneous.toml @@ -0,0 +1 @@ +mixed = [[1, 2], ["a", "b"], [1.0, 2.0]] diff --git a/src/test/valid/arrays-nested.json b/src/test/valid/arrays-nested.json new file mode 100644 index 0000000..d21920c --- /dev/null +++ b/src/test/valid/arrays-nested.json @@ -0,0 +1,13 @@ +{ + "nest": { + "type": "array", + "value": [ + {"type": "array", "value": [ + {"type": "string", "value": "a"} + ]}, + {"type": "array", "value": [ + {"type": "string", "value": "b"} + ]} + ] + } +} diff --git a/src/test/valid/arrays-nested.toml b/src/test/valid/arrays-nested.toml new file mode 100644 index 0000000..ce33022 --- /dev/null +++ b/src/test/valid/arrays-nested.toml @@ -0,0 +1 @@ +nest = [["a"], ["b"]] diff --git a/src/test/valid/arrays.json b/src/test/valid/arrays.json new file mode 100644 index 0000000..4d16d8a --- /dev/null +++ b/src/test/valid/arrays.json @@ -0,0 +1,34 @@ +{ + "ints": { + "type": "array", + "value": [ + {"type": "integer", "value": "1"}, + {"type": "integer", "value": "2"}, + {"type": "integer", "value": "3"} + ] + }, + "floats": { + "type": "array", + "value": [ + {"type": "float", "value": "1.0"}, + {"type": "float", "value": "2.0"}, + {"type": "float", "value": "3.0"} + ] + }, + "strings": { + "type": "array", + "value": [ + {"type": "string", "value": "a"}, + {"type": "string", "value": "b"}, + {"type": "string", "value": "c"} + ] + }, + "dates": { + "type": "array", + "value": [ + {"type": "datetime", "value": "1987-07-05T17:45:00Z"}, + {"type": "datetime", "value": "1979-05-27T07:32:00Z"}, + {"type": "datetime", "value": "2006-06-01T11:00:00Z"} + ] + } +} diff --git a/src/test/valid/arrays.toml b/src/test/valid/arrays.toml new file mode 100644 index 0000000..6d6440d --- /dev/null +++ b/src/test/valid/arrays.toml @@ -0,0 +1,9 @@ +ints = [1, 2, 3] +floats = [1.0, 2.0, 3.0] +strings = ["a", "b", "c"] +dates = [ + 1987-07-05T17:45:00Z, + 1979-05-27T07:32:00Z, + 2006-06-01T11:00:00Z, +] + diff --git a/src/test/valid/bool.json b/src/test/valid/bool.json new file mode 100644 index 0000000..ae368e9 --- /dev/null +++ b/src/test/valid/bool.json @@ -0,0 +1,4 @@ +{ + "f": {"type": "bool", "value": "false"}, + "t": {"type": "bool", "value": "true"} +} diff --git a/src/test/valid/bool.toml b/src/test/valid/bool.toml new file mode 100644 index 0000000..a8a829b --- /dev/null +++ b/src/test/valid/bool.toml @@ -0,0 +1,2 @@ +t = true +f = false diff --git a/src/test/valid/comments-everywhere.json b/src/test/valid/comments-everywhere.json new file mode 100644 index 0000000..e69a2e9 --- /dev/null +++ b/src/test/valid/comments-everywhere.json @@ -0,0 +1,12 @@ +{ + "group": { + "answer": {"type": "integer", "value": "42"}, + "more": { + "type": "array", + "value": [ + {"type": "integer", "value": "42"}, + {"type": "integer", "value": "42"} + ] + } + } +} diff --git a/src/test/valid/comments-everywhere.toml b/src/test/valid/comments-everywhere.toml new file mode 100644 index 0000000..3dca74c --- /dev/null +++ b/src/test/valid/comments-everywhere.toml @@ -0,0 +1,24 @@ +# Top comment. + # Top comment. +# Top comment. + +# [no-extraneous-groups-please] + +[group] # Comment +answer = 42 # Comment +# no-extraneous-keys-please = 999 +# Inbetween comment. +more = [ # Comment + # What about multiple # comments? + # Can you handle it? + # + # Evil. +# Evil. + 42, 42, # Comments within arrays are fun. + # What about multiple # comments? + # Can you handle it? + # + # Evil. +# Evil. +# ] Did I fool you? +] # Hopefully not. diff --git a/src/test/valid/datetime.json b/src/test/valid/datetime.json new file mode 100644 index 0000000..2ca93ce --- /dev/null +++ b/src/test/valid/datetime.json @@ -0,0 +1,3 @@ +{ + "bestdayever": {"type": "datetime", "value": "1987-07-05T17:45:00Z"} +} diff --git a/src/test/valid/datetime.toml b/src/test/valid/datetime.toml new file mode 100644 index 0000000..2e99340 --- /dev/null +++ b/src/test/valid/datetime.toml @@ -0,0 +1 @@ +bestdayever = 1987-07-05T17:45:00Z diff --git a/src/test/valid/empty.json b/src/test/valid/empty.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/test/valid/empty.json @@ -0,0 +1 @@ +{} diff --git a/src/test/valid/empty.toml b/src/test/valid/empty.toml new file mode 100644 index 0000000..e69de29 diff --git a/src/test/valid/example.json b/src/test/valid/example.json new file mode 100644 index 0000000..48aa907 --- /dev/null +++ b/src/test/valid/example.json @@ -0,0 +1,14 @@ +{ + "best-day-ever": {"type": "datetime", "value": "1987-07-05T17:45:00Z"}, + "numtheory": { + "boring": {"type": "bool", "value": "false"}, + "perfection": { + "type": "array", + "value": [ + {"type": "integer", "value": "6"}, + {"type": "integer", "value": "28"}, + {"type": "integer", "value": "496"} + ] + } + } +} diff --git a/src/test/valid/example.toml b/src/test/valid/example.toml new file mode 100644 index 0000000..8cb02e0 --- /dev/null +++ b/src/test/valid/example.toml @@ -0,0 +1,5 @@ +best-day-ever = 1987-07-05T17:45:00Z + +[numtheory] +boring = false +perfection = [6, 28, 496] diff --git a/src/test/valid/float.json b/src/test/valid/float.json new file mode 100644 index 0000000..b8a2e97 --- /dev/null +++ b/src/test/valid/float.json @@ -0,0 +1,4 @@ +{ + "pi": {"type": "float", "value": "3.14"}, + "negpi": {"type": "float", "value": "-3.14"} +} diff --git a/src/test/valid/float.toml b/src/test/valid/float.toml new file mode 100644 index 0000000..7c528d2 --- /dev/null +++ b/src/test/valid/float.toml @@ -0,0 +1,2 @@ +pi = 3.14 +negpi = -3.14 diff --git a/src/test/valid/implicit-and-explicit-after.json b/src/test/valid/implicit-and-explicit-after.json new file mode 100644 index 0000000..374bd09 --- /dev/null +++ b/src/test/valid/implicit-and-explicit-after.json @@ -0,0 +1,10 @@ +{ + "a": { + "better": {"type": "integer", "value": "43"}, + "b": { + "c": { + "answer": {"type": "integer", "value": "42"} + } + } + } +} diff --git a/src/test/valid/implicit-and-explicit-after.toml b/src/test/valid/implicit-and-explicit-after.toml new file mode 100644 index 0000000..c0e8865 --- /dev/null +++ b/src/test/valid/implicit-and-explicit-after.toml @@ -0,0 +1,5 @@ +[a.b.c] +answer = 42 + +[a] +better = 43 diff --git a/src/test/valid/implicit-and-explicit-before.json b/src/test/valid/implicit-and-explicit-before.json new file mode 100644 index 0000000..374bd09 --- /dev/null +++ b/src/test/valid/implicit-and-explicit-before.json @@ -0,0 +1,10 @@ +{ + "a": { + "better": {"type": "integer", "value": "43"}, + "b": { + "c": { + "answer": {"type": "integer", "value": "42"} + } + } + } +} diff --git a/src/test/valid/implicit-and-explicit-before.toml b/src/test/valid/implicit-and-explicit-before.toml new file mode 100644 index 0000000..eee68ff --- /dev/null +++ b/src/test/valid/implicit-and-explicit-before.toml @@ -0,0 +1,5 @@ +[a] +better = 43 + +[a.b.c] +answer = 42 diff --git a/src/test/valid/implicit-groups.json b/src/test/valid/implicit-groups.json new file mode 100644 index 0000000..fbae7fc --- /dev/null +++ b/src/test/valid/implicit-groups.json @@ -0,0 +1,9 @@ +{ + "a": { + "b": { + "c": { + "answer": {"type": "integer", "value": "42"} + } + } + } +} diff --git a/src/test/valid/implicit-groups.toml b/src/test/valid/implicit-groups.toml new file mode 100644 index 0000000..b6333e4 --- /dev/null +++ b/src/test/valid/implicit-groups.toml @@ -0,0 +1,2 @@ +[a.b.c] +answer = 42 diff --git a/src/test/valid/integer.json b/src/test/valid/integer.json new file mode 100644 index 0000000..61985a1 --- /dev/null +++ b/src/test/valid/integer.json @@ -0,0 +1,4 @@ +{ + "answer": {"type": "integer", "value": "42"}, + "neganswer": {"type": "integer", "value": "-42"} +} diff --git a/src/test/valid/integer.toml b/src/test/valid/integer.toml new file mode 100644 index 0000000..c4f6297 --- /dev/null +++ b/src/test/valid/integer.toml @@ -0,0 +1,2 @@ +answer = 42 +neganswer = -42 diff --git a/src/test/valid/key-equals-nospace.json b/src/test/valid/key-equals-nospace.json new file mode 100644 index 0000000..1f8709a --- /dev/null +++ b/src/test/valid/key-equals-nospace.json @@ -0,0 +1,3 @@ +{ + "answer": {"type": "integer", "value": "42"} +} diff --git a/src/test/valid/key-equals-nospace.toml b/src/test/valid/key-equals-nospace.toml new file mode 100644 index 0000000..560901c --- /dev/null +++ b/src/test/valid/key-equals-nospace.toml @@ -0,0 +1 @@ +answer=42 diff --git a/src/test/valid/key-special-chars.json b/src/test/valid/key-special-chars.json new file mode 100644 index 0000000..6550ebd --- /dev/null +++ b/src/test/valid/key-special-chars.json @@ -0,0 +1,5 @@ +{ + "~!@#$^&*()_+-`1234567890[]\\|/?><.,;:'": { + "type": "integer", "value": "1" + } +} diff --git a/src/test/valid/key-special-chars.toml b/src/test/valid/key-special-chars.toml new file mode 100644 index 0000000..8b3fc51 --- /dev/null +++ b/src/test/valid/key-special-chars.toml @@ -0,0 +1 @@ +~!@#$^&*()_+-`1234567890[]\|/?><.,;:' = 1 diff --git a/src/test/valid/key-with-pound.json b/src/test/valid/key-with-pound.json new file mode 100644 index 0000000..ee39e1d --- /dev/null +++ b/src/test/valid/key-with-pound.json @@ -0,0 +1,3 @@ +{ + "key#name": {"type": "integer", "value": "5"} +} diff --git a/src/test/valid/key-with-pound.toml b/src/test/valid/key-with-pound.toml new file mode 100644 index 0000000..1c54f53 --- /dev/null +++ b/src/test/valid/key-with-pound.toml @@ -0,0 +1 @@ +key#name = 5 diff --git a/src/test/valid/long-float.json b/src/test/valid/long-float.json new file mode 100644 index 0000000..8ceed47 --- /dev/null +++ b/src/test/valid/long-float.json @@ -0,0 +1,4 @@ +{ + "longpi": {"type": "float", "value": "3.141592653589793"}, + "neglongpi": {"type": "float", "value": "-3.141592653589793"} +} diff --git a/src/test/valid/long-float.toml b/src/test/valid/long-float.toml new file mode 100644 index 0000000..9558ae4 --- /dev/null +++ b/src/test/valid/long-float.toml @@ -0,0 +1,2 @@ +longpi = 3.141592653589793 +neglongpi = -3.141592653589793 diff --git a/src/test/valid/long-integer.json b/src/test/valid/long-integer.json new file mode 100644 index 0000000..16c331e --- /dev/null +++ b/src/test/valid/long-integer.json @@ -0,0 +1,4 @@ +{ + "answer": {"type": "integer", "value": "9223372036854775807"}, + "neganswer": {"type": "integer", "value": "-9223372036854775808"} +} diff --git a/src/test/valid/long-integer.toml b/src/test/valid/long-integer.toml new file mode 100644 index 0000000..424a13a --- /dev/null +++ b/src/test/valid/long-integer.toml @@ -0,0 +1,2 @@ +answer = 9223372036854775807 +neganswer = -9223372036854775808 diff --git a/src/test/valid/string-escapes.json b/src/test/valid/string-escapes.json new file mode 100644 index 0000000..ca71d30 --- /dev/null +++ b/src/test/valid/string-escapes.json @@ -0,0 +1,34 @@ +{ + "backspace": { + "type": "string", + "value": "This string has a \u0008 backspace character." + }, + "tab": { + "type": "string", + "value": "This string has a \u0009 tab character." + }, + "newline": { + "type": "string", + "value": "This string has a \u000A new line character." + }, + "formfeed": { + "type": "string", + "value": "This string has a \u000C form feed character." + }, + "carriage": { + "type": "string", + "value": "This string has a \u000D carriage return character." + }, + "quote": { + "type": "string", + "value": "This string has a \u0022 quote character." + }, + "slash": { + "type": "string", + "value": "This string has a \u002F slash character." + }, + "backslash": { + "type": "string", + "value": "This string has a \u005C backslash character." + } +} diff --git a/src/test/valid/string-escapes.toml b/src/test/valid/string-escapes.toml new file mode 100644 index 0000000..2d64500 --- /dev/null +++ b/src/test/valid/string-escapes.toml @@ -0,0 +1,8 @@ +backspace = "This string has a \b backspace character." +tab = "This string has a \t tab character." +newline = "This string has a \n new line character." +formfeed = "This string has a \f form feed character." +carriage = "This string has a \r carriage return character." +quote = "This string has a \" quote character." +slash = "This string has a \/ slash character." +backslash = "This string has a \\ backslash character." diff --git a/src/test/valid/string-simple.json b/src/test/valid/string-simple.json new file mode 100644 index 0000000..2e05f99 --- /dev/null +++ b/src/test/valid/string-simple.json @@ -0,0 +1,6 @@ +{ + "answer": { + "type": "string", + "value": "You are not drinking enough whisky." + } +} diff --git a/src/test/valid/string-simple.toml b/src/test/valid/string-simple.toml new file mode 100644 index 0000000..e17ade6 --- /dev/null +++ b/src/test/valid/string-simple.toml @@ -0,0 +1 @@ +answer = "You are not drinking enough whisky." diff --git a/src/test/valid/string-with-pound.json b/src/test/valid/string-with-pound.json new file mode 100644 index 0000000..33cdc9c --- /dev/null +++ b/src/test/valid/string-with-pound.json @@ -0,0 +1,7 @@ +{ + "pound": {"type": "string", "value": "We see no # comments here."}, + "poundcomment": { + "type": "string", + "value": "But there are # some comments here." + } +} diff --git a/src/test/valid/string-with-pound.toml b/src/test/valid/string-with-pound.toml new file mode 100644 index 0000000..5fd8746 --- /dev/null +++ b/src/test/valid/string-with-pound.toml @@ -0,0 +1,2 @@ +pound = "We see no # comments here." +poundcomment = "But there are # some comments here." # Did I # mess you up? diff --git a/src/test/valid/table-array-implicit.json b/src/test/valid/table-array-implicit.json new file mode 100644 index 0000000..32e4640 --- /dev/null +++ b/src/test/valid/table-array-implicit.json @@ -0,0 +1,7 @@ +{ + "albums": { + "songs": [ + {"name": {"type": "string", "value": "Glory Days"}} + ] + } +} diff --git a/src/test/valid/table-array-implicit.toml b/src/test/valid/table-array-implicit.toml new file mode 100644 index 0000000..3157ac9 --- /dev/null +++ b/src/test/valid/table-array-implicit.toml @@ -0,0 +1,2 @@ +[[albums.songs]] +name = "Glory Days" diff --git a/src/test/valid/table-array-many.json b/src/test/valid/table-array-many.json new file mode 100644 index 0000000..84df2da --- /dev/null +++ b/src/test/valid/table-array-many.json @@ -0,0 +1,16 @@ +{ + "people": [ + { + "first_name": {"type": "string", "value": "Bruce"}, + "last_name": {"type": "string", "value": "Springsteen"} + }, + { + "first_name": {"type": "string", "value": "Eric"}, + "last_name": {"type": "string", "value": "Clapton"} + }, + { + "first_name": {"type": "string", "value": "Bob"}, + "last_name": {"type": "string", "value": "Seger"} + } + ] +} diff --git a/src/test/valid/table-array-many.toml b/src/test/valid/table-array-many.toml new file mode 100644 index 0000000..46062be --- /dev/null +++ b/src/test/valid/table-array-many.toml @@ -0,0 +1,11 @@ +[[people]] +first_name = "Bruce" +last_name = "Springsteen" + +[[people]] +first_name = "Eric" +last_name = "Clapton" + +[[people]] +first_name = "Bob" +last_name = "Seger" diff --git a/src/test/valid/table-array-nest.json b/src/test/valid/table-array-nest.json new file mode 100644 index 0000000..c117afa --- /dev/null +++ b/src/test/valid/table-array-nest.json @@ -0,0 +1,18 @@ +{ + "albums": [ + { + "name": {"type": "string", "value": "Born to Run"}, + "songs": [ + {"name": {"type": "string", "value": "Jungleland"}}, + {"name": {"type": "string", "value": "Meeting Across the River"}} + ] + }, + { + "name": {"type": "string", "value": "Born in the USA"}, + "songs": [ + {"name": {"type": "string", "value": "Glory Days"}}, + {"name": {"type": "string", "value": "Dancing in the Dark"}} + ] + } + ] +} diff --git a/src/test/valid/table-array-nest.toml b/src/test/valid/table-array-nest.toml new file mode 100644 index 0000000..d659a3d --- /dev/null +++ b/src/test/valid/table-array-nest.toml @@ -0,0 +1,17 @@ +[[albums]] +name = "Born to Run" + + [[albums.songs]] + name = "Jungleland" + + [[albums.songs]] + name = "Meeting Across the River" + +[[albums]] +name = "Born in the USA" + + [[albums.songs]] + name = "Glory Days" + + [[albums.songs]] + name = "Dancing in the Dark" diff --git a/src/test/valid/table-array-one.json b/src/test/valid/table-array-one.json new file mode 100644 index 0000000..d75faae --- /dev/null +++ b/src/test/valid/table-array-one.json @@ -0,0 +1,8 @@ +{ + "people": [ + { + "first_name": {"type": "string", "value": "Bruce"}, + "last_name": {"type": "string", "value": "Springsteen"} + } + ] +} diff --git a/src/test/valid/table-array-one.toml b/src/test/valid/table-array-one.toml new file mode 100644 index 0000000..cd7e1b6 --- /dev/null +++ b/src/test/valid/table-array-one.toml @@ -0,0 +1,3 @@ +[[people]] +first_name = "Bruce" +last_name = "Springsteen" diff --git a/src/test/valid/table-empty.json b/src/test/valid/table-empty.json new file mode 100644 index 0000000..6f3873a --- /dev/null +++ b/src/test/valid/table-empty.json @@ -0,0 +1,3 @@ +{ + "a": {} +} diff --git a/src/test/valid/table-empty.toml b/src/test/valid/table-empty.toml new file mode 100644 index 0000000..8bb6a0a --- /dev/null +++ b/src/test/valid/table-empty.toml @@ -0,0 +1 @@ +[a] diff --git a/src/test/valid/table-sub-empty.json b/src/test/valid/table-sub-empty.json new file mode 100644 index 0000000..9787770 --- /dev/null +++ b/src/test/valid/table-sub-empty.json @@ -0,0 +1,3 @@ +{ + "a": { "b": {} } +} diff --git a/src/test/valid/table-sub-empty.toml b/src/test/valid/table-sub-empty.toml new file mode 100644 index 0000000..70b7fe1 --- /dev/null +++ b/src/test/valid/table-sub-empty.toml @@ -0,0 +1,2 @@ +[a] +[a.b] diff --git a/src/test/valid/table-whitespace.json b/src/test/valid/table-whitespace.json new file mode 100644 index 0000000..3a73ec8 --- /dev/null +++ b/src/test/valid/table-whitespace.json @@ -0,0 +1,3 @@ +{ + "valid key": {} +} diff --git a/src/test/valid/table-whitespace.toml b/src/test/valid/table-whitespace.toml new file mode 100644 index 0000000..798756c --- /dev/null +++ b/src/test/valid/table-whitespace.toml @@ -0,0 +1 @@ +[valid key] diff --git a/src/test/valid/table-with-pound.json b/src/test/valid/table-with-pound.json new file mode 100644 index 0000000..5e594e4 --- /dev/null +++ b/src/test/valid/table-with-pound.json @@ -0,0 +1,5 @@ +{ + "key#group": { + "answer": {"type": "integer", "value": "42"} + } +} diff --git a/src/test/valid/table-with-pound.toml b/src/test/valid/table-with-pound.toml new file mode 100644 index 0000000..e7b777e --- /dev/null +++ b/src/test/valid/table-with-pound.toml @@ -0,0 +1,2 @@ +[key#group] +answer = 42 diff --git a/src/test/valid/unicode-escape.json b/src/test/valid/unicode-escape.json new file mode 100644 index 0000000..deda62c --- /dev/null +++ b/src/test/valid/unicode-escape.json @@ -0,0 +1,3 @@ +{ + "answer": {"type": "string", "value": "\u03B4"} +} diff --git a/src/test/valid/unicode-escape.toml b/src/test/valid/unicode-escape.toml new file mode 100644 index 0000000..057ce15 --- /dev/null +++ b/src/test/valid/unicode-escape.toml @@ -0,0 +1 @@ +answer = "\u03B4" diff --git a/src/test/valid/unicode-literal.json b/src/test/valid/unicode-literal.json new file mode 100644 index 0000000..00aa2f8 --- /dev/null +++ b/src/test/valid/unicode-literal.json @@ -0,0 +1,3 @@ +{ + "answer": {"type": "string", "value": "δ"} +} diff --git a/src/test/valid/unicode-literal.toml b/src/test/valid/unicode-literal.toml new file mode 100644 index 0000000..c65723c --- /dev/null +++ b/src/test/valid/unicode-literal.toml @@ -0,0 +1 @@ +answer = "δ" diff --git a/src/toml.rs b/src/toml.rs new file mode 100644 index 0000000..cebdb37 --- /dev/null +++ b/src/toml.rs @@ -0,0 +1,51 @@ +#![crate_type = "lib"] +#![feature(macro_rules)] + +use std::collections::HashMap; + +pub use parser::{Parser, Error}; + +mod parser; +#[cfg(test)] +mod test; + +pub enum Value { + String(String), + Integer(i64), + Float(f64), + Boolean(bool), + Datetime(String), + Array(Array), + Table(Table), +} + +pub type Array = Vec; +pub type Table = HashMap; + +impl Value { + fn same_type(&self, other: &Value) -> bool { + match (self, other) { + (&String(..), &String(..)) | + (&Integer(..), &Integer(..)) | + (&Float(..), &Float(..)) | + (&Boolean(..), &Boolean(..)) | + (&Datetime(..), &Datetime(..)) | + (&Array(..), &Array(..)) | + (&Table(..), &Table(..)) => true, + + _ => false, + } + } + + fn type_str(&self) -> &'static str { + match *self { + String(..) => "string", + Integer(..) => "integer", + Float(..) => "float", + Boolean(..) => "boolean", + Datetime(..) => "datetime", + Array(..) => "array", + Table(..) => "table", + } + } +}