aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Crichton <alex@alexcrichton.com>2014-06-20 17:01:38 -0700
committerAlex Crichton <alex@alexcrichton.com>2014-06-20 17:01:38 -0700
commit11115f13a3499420cd09b745a298ef071755b24b (patch)
treeea24118086e8a1f814c4cd9b2f617c93fe107754
downloadmilf-rs-11115f13a3499420cd09b745a298ef071755b24b.tar.gz
milf-rs-11115f13a3499420cd09b745a298ef071755b24b.zip
Initial commit
-rw-r--r--src/parser.rs617
-rw-r--r--src/test/README.md1
-rw-r--r--src/test/invalid-encoder/array-mixed-types-ints-and-floats.json15
-rw-r--r--src/test/invalid.rs76
-rw-r--r--src/test/invalid/array-mixed-types-arrays-and-ints.toml1
-rw-r--r--src/test/invalid/array-mixed-types-ints-and-floats.toml1
-rw-r--r--src/test/invalid/array-mixed-types-strings-and-ints.toml1
-rw-r--r--src/test/invalid/datetime-malformed-no-leads.toml1
-rw-r--r--src/test/invalid/datetime-malformed-no-secs.toml1
-rw-r--r--src/test/invalid/datetime-malformed-no-t.toml1
-rw-r--r--src/test/invalid/datetime-malformed-no-z.toml1
-rw-r--r--src/test/invalid/datetime-malformed-with-milli.toml1
-rw-r--r--src/test/invalid/duplicate-key-table.toml5
-rw-r--r--src/test/invalid/duplicate-keys.toml2
-rw-r--r--src/test/invalid/duplicate-tables.toml2
-rw-r--r--src/test/invalid/empty-implicit-table.toml1
-rw-r--r--src/test/invalid/empty-table.toml1
-rw-r--r--src/test/invalid/float-no-leading-zero.toml2
-rw-r--r--src/test/invalid/float-no-trailing-digits.toml2
-rw-r--r--src/test/invalid/key-two-equals.toml1
-rw-r--r--src/test/invalid/string-bad-byte-escape.toml1
-rw-r--r--src/test/invalid/string-bad-escape.toml1
-rw-r--r--src/test/invalid/string-byte-escapes.toml1
-rw-r--r--src/test/invalid/string-no-close.toml1
-rw-r--r--src/test/invalid/table-array-implicit.toml14
-rw-r--r--src/test/invalid/table-array-malformed-bracket.toml2
-rw-r--r--src/test/invalid/table-array-malformed-empty.toml2
-rw-r--r--src/test/invalid/table-nested-brackets-close.toml2
-rw-r--r--src/test/invalid/table-nested-brackets-open.toml2
-rw-r--r--src/test/invalid/text-after-array-entries.toml4
-rw-r--r--src/test/invalid/text-after-integer.toml1
-rw-r--r--src/test/invalid/text-after-string.toml1
-rw-r--r--src/test/invalid/text-after-table.toml1
-rw-r--r--src/test/invalid/text-before-array-separator.toml4
-rw-r--r--src/test/invalid/text-in-array.toml5
-rw-r--r--src/test/mod.rs2
-rw-r--r--src/test/valid.rs163
-rw-r--r--src/test/valid/array-empty.json11
-rw-r--r--src/test/valid/array-empty.toml1
-rw-r--r--src/test/valid/array-nospaces.json10
-rw-r--r--src/test/valid/array-nospaces.toml1
-rw-r--r--src/test/valid/arrays-hetergeneous.json19
-rw-r--r--src/test/valid/arrays-hetergeneous.toml1
-rw-r--r--src/test/valid/arrays-nested.json13
-rw-r--r--src/test/valid/arrays-nested.toml1
-rw-r--r--src/test/valid/arrays.json34
-rw-r--r--src/test/valid/arrays.toml9
-rw-r--r--src/test/valid/bool.json4
-rw-r--r--src/test/valid/bool.toml2
-rw-r--r--src/test/valid/comments-everywhere.json12
-rw-r--r--src/test/valid/comments-everywhere.toml24
-rw-r--r--src/test/valid/datetime.json3
-rw-r--r--src/test/valid/datetime.toml1
-rw-r--r--src/test/valid/empty.json1
-rw-r--r--src/test/valid/empty.toml0
-rw-r--r--src/test/valid/example.json14
-rw-r--r--src/test/valid/example.toml5
-rw-r--r--src/test/valid/float.json4
-rw-r--r--src/test/valid/float.toml2
-rw-r--r--src/test/valid/implicit-and-explicit-after.json10
-rw-r--r--src/test/valid/implicit-and-explicit-after.toml5
-rw-r--r--src/test/valid/implicit-and-explicit-before.json10
-rw-r--r--src/test/valid/implicit-and-explicit-before.toml5
-rw-r--r--src/test/valid/implicit-groups.json9
-rw-r--r--src/test/valid/implicit-groups.toml2
-rw-r--r--src/test/valid/integer.json4
-rw-r--r--src/test/valid/integer.toml2
-rw-r--r--src/test/valid/key-equals-nospace.json3
-rw-r--r--src/test/valid/key-equals-nospace.toml1
-rw-r--r--src/test/valid/key-special-chars.json5
-rw-r--r--src/test/valid/key-special-chars.toml1
-rw-r--r--src/test/valid/key-with-pound.json3
-rw-r--r--src/test/valid/key-with-pound.toml1
-rw-r--r--src/test/valid/long-float.json4
-rw-r--r--src/test/valid/long-float.toml2
-rw-r--r--src/test/valid/long-integer.json4
-rw-r--r--src/test/valid/long-integer.toml2
-rw-r--r--src/test/valid/string-escapes.json34
-rw-r--r--src/test/valid/string-escapes.toml8
-rw-r--r--src/test/valid/string-simple.json6
-rw-r--r--src/test/valid/string-simple.toml1
-rw-r--r--src/test/valid/string-with-pound.json7
-rw-r--r--src/test/valid/string-with-pound.toml2
-rw-r--r--src/test/valid/table-array-implicit.json7
-rw-r--r--src/test/valid/table-array-implicit.toml2
-rw-r--r--src/test/valid/table-array-many.json16
-rw-r--r--src/test/valid/table-array-many.toml11
-rw-r--r--src/test/valid/table-array-nest.json18
-rw-r--r--src/test/valid/table-array-nest.toml17
-rw-r--r--src/test/valid/table-array-one.json8
-rw-r--r--src/test/valid/table-array-one.toml3
-rw-r--r--src/test/valid/table-empty.json3
-rw-r--r--src/test/valid/table-empty.toml1
-rw-r--r--src/test/valid/table-sub-empty.json3
-rw-r--r--src/test/valid/table-sub-empty.toml2
-rw-r--r--src/test/valid/table-whitespace.json3
-rw-r--r--src/test/valid/table-whitespace.toml1
-rw-r--r--src/test/valid/table-with-pound.json5
-rw-r--r--src/test/valid/table-with-pound.toml2
-rw-r--r--src/test/valid/unicode-escape.json3
-rw-r--r--src/test/valid/unicode-escape.toml1
-rw-r--r--src/test/valid/unicode-literal.json3
-rw-r--r--src/test/valid/unicode-literal.toml1
-rw-r--r--src/toml.rs51
104 files changed, 1404 insertions, 0 deletions
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<String>,
+ pub errors: Vec<Error>,
+}
+
+#[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<Table> {
+ 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<Value> {
+ 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<Value> {
+ 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<char> {
+ 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<Value> {
+ 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::<f64>(self.input.slice(start, end)).map(Float)
+ }
+ } else if !negative && self.eat('-') {
+ self.datetime(start, end + 1)
+ } else {
+ from_str::<i64>(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<Value> {
+ 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<Value> {
+ 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<Value> {
+ 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::<Vec<(String, &str)>>());
+ 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
--- /dev/null
+++ b/src/test/valid/empty.toml
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<Value>;
+pub type Table = HashMap<String, Value>;
+
+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",
+ }
+ }
+}