Update key syntax to TOML master

* Bare keys contain a very limited set of characters now.
* Keys in quotes behave the same as basic strings.

Closes #47
This commit is contained in:
Alex Crichton 2015-01-15 19:47:14 -08:00
parent 46a70861d5
commit 3536cb50e6
5 changed files with 113 additions and 93 deletions

View file

@ -158,80 +158,85 @@ impl<'a> Parser<'a> {
/// to determine the cause of the parse failure. /// to determine the cause of the parse failure.
pub fn parse(&mut self) -> Option<TomlTable> { pub fn parse(&mut self) -> Option<TomlTable> {
let mut ret = BTreeMap::new(); let mut ret = BTreeMap::new();
loop { while self.peek(0).is_some() {
self.ws(); self.ws();
if self.newline() { continue } if self.newline() { continue }
match self.peek(0) { if self.comment() { continue }
Some((_, '#')) => { self.comment(); } if self.eat('[') {
Some((start, '[')) => { let array = self.eat('[');
self.cur.next(); let start = self.next_pos();
let array = self.eat('[');
// Parse the name of the section // Parse the name of the section
let mut section = String::new(); let mut keys = Vec::new();
for (pos, ch) in self.cur { loop {
if ch == ']' { break } self.ws();
if ch == '[' { match self.key_name() {
self.errors.push(ParserError { Some(s) => keys.push(s),
lo: pos, None => {}
hi: pos + 1,
desc: format!("section names cannot contain \
a `[` character"),
});
continue
}
section.push(ch);
} }
self.ws();
if section.len() == 0 { if self.eat(']') {
self.errors.push(ParserError { if array && !self.expect(']') { return None }
lo: start, break
hi: start + if array {3} else {1},
desc: format!("section name must not be empty"),
});
continue
} else if array && !self.expect(']') {
return None
}
// Build the section table
let mut table = BTreeMap::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)
} }
if !self.expect('.') { return None }
} }
Some(_) => { if keys.len() == 0 { return None }
if !self.values(&mut ret) { return None }
// Build the section table
let mut table = BTreeMap::new();
if !self.values(&mut table) { return None }
if array {
self.insert_array(&mut ret, &keys[], Table(table), start)
} else {
self.insert_table(&mut ret, &keys[], table, start)
} }
None if self.errors.len() == 0 => return Some(ret), } else {
None => return None, if !self.values(&mut ret) { return None }
} }
} }
if self.errors.len() > 0 {
None
} else {
Some(ret)
}
} }
fn key_name(&mut self, start: usize) -> Option<String> { // Parse a single key name starting at `start`
if self.eat('"') { fn key_name(&mut self) -> Option<String> {
return self.finish_string(start, false); let start = self.next_pos();
} let key = if self.eat('"') {
let mut ret = String::new(); self.finish_string(start, false)
loop { } else {
match self.cur.clone().next() { let mut ret = String::new();
Some((_, ch)) => { loop {
match ch { match self.cur.clone().next() {
'a' ... 'z' | Some((_, ch)) => {
'A' ... 'Z' | match ch {
'0' ... '9' | 'a' ... 'z' |
'_' | '-' => { self.cur.next(); ret.push(ch) } 'A' ... 'Z' |
_ => break, '0' ... '9' |
'_' | '-' => { self.cur.next(); ret.push(ch) }
_ => break,
}
} }
None => {}
} }
None => {}
} }
Some(ret)
};
match key {
Some(ref name) if name.len() == 0 => {
self.errors.push(ParserError {
lo: start,
hi: start,
desc: format!("expected a key but found an empty string"),
});
None
}
Some(name) => Some(name),
None => None,
} }
Some(ret)
} }
// Parses the values into the given TomlTable. Returns true in case of success // Parses the values into the given TomlTable. Returns true in case of success
@ -247,7 +252,7 @@ impl<'a> Parser<'a> {
None => break, None => break,
} }
let key_lo = self.next_pos(); let key_lo = self.next_pos();
let key = match self.key_name(key_lo) { let key = match self.key_name() {
Some(s) => s, Some(s) => s,
None => return false None => return false
}; };
@ -675,38 +680,25 @@ impl<'a> Parser<'a> {
} }
} }
fn recurse<'b>(&mut self, mut cur: &'b mut TomlTable, orig_key: &'b str, fn recurse<'b>(&mut self, mut cur: &'b mut TomlTable, keys: &'b [String],
key_lo: usize) -> Option<(&'b mut TomlTable, &'b str)> { key_lo: usize) -> Option<(&'b mut TomlTable, &'b str)> {
if orig_key.starts_with(".") || orig_key.ends_with(".") || let key_hi = keys.iter().fold(0, |a, b| a + b.len());
orig_key.contains("..") { for part in keys[..keys.len() - 1].iter() {
self.errors.push(ParserError {
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; let tmp = cur;
if tmp.contains_key(&part) { if tmp.contains_key(part) {
match *tmp.get_mut(&part).unwrap() { match *tmp.get_mut(part).unwrap() {
Table(ref mut table) => { Table(ref mut table) => {
cur = table; cur = table;
continue continue
} }
Array(ref mut array) => { Array(ref mut array) => {
match array.as_mut_slice().last_mut() { match array.last_mut() {
Some(&mut Table(ref mut table)) => cur = table, Some(&mut Table(ref mut table)) => cur = table,
_ => { _ => {
self.errors.push(ParserError { self.errors.push(ParserError {
lo: key_lo, lo: key_lo,
hi: key_lo + key.len(), hi: key_hi,
desc: format!("array `{}` does not contain \ desc: format!("array `{}` does not contain \
tables", part) tables", part)
}); });
@ -718,7 +710,7 @@ impl<'a> Parser<'a> {
_ => { _ => {
self.errors.push(ParserError { self.errors.push(ParserError {
lo: key_lo, lo: key_lo,
hi: key_lo + key.len(), hi: key_hi,
desc: format!("key `{}` was not previously a table", desc: format!("key `{}` was not previously a table",
part) part)
}); });
@ -729,17 +721,17 @@ impl<'a> Parser<'a> {
// Initialize an empty table as part of this sub-key // Initialize an empty table as part of this sub-key
tmp.insert(part.clone(), Table(BTreeMap::new())); tmp.insert(part.clone(), Table(BTreeMap::new()));
match *tmp.get_mut(&part).unwrap() { match *tmp.get_mut(part).unwrap() {
Table(ref mut inner) => cur = inner, Table(ref mut inner) => cur = inner,
_ => unreachable!(), _ => unreachable!(),
} }
} }
return Some((cur, orig_key.slice_from(key.len() + 1))) Some((cur, &keys.last().unwrap()[]))
} }
fn insert_table(&mut self, into: &mut TomlTable, key: String, value: TomlTable, fn insert_table(&mut self, into: &mut TomlTable, keys: &[String],
key_lo: usize) { value: TomlTable, key_lo: usize) {
let (into, key) = match self.recurse(into, key.as_slice(), key_lo) { let (into, key) = match self.recurse(into, keys, key_lo) {
Some(pair) => pair, Some(pair) => pair,
None => return, None => return,
}; };
@ -780,9 +772,9 @@ impl<'a> Parser<'a> {
} }
} }
fn insert_array(&mut self, into: &mut TomlTable, key: String, value: Value, fn insert_array(&mut self, into: &mut TomlTable,
key_lo: usize) { keys: &[String], value: Value, key_lo: usize) {
let (into, key) = match self.recurse(into, key.as_slice(), key_lo) { let (into, key) = match self.recurse(into, keys, key_lo) {
Some(pair) => pair, Some(pair) => pair,
None => return, None => return,
}; };
@ -1111,8 +1103,36 @@ trimmed in raw strings.
assert!(Parser::new("key\n=3").parse().is_none()); assert!(Parser::new("key\n=3").parse().is_none());
assert!(Parser::new("key=\n3").parse().is_none()); assert!(Parser::new("key=\n3").parse().is_none());
assert!(Parser::new("key|=3").parse().is_none()); assert!(Parser::new("key|=3").parse().is_none());
assert!(Parser::new("\"\"=3").parse().is_none());
assert!(Parser::new("=3").parse().is_none());
assert!(Parser::new("\"\"|=3").parse().is_none()); assert!(Parser::new("\"\"|=3").parse().is_none());
assert!(Parser::new("\"\n\"|=3").parse().is_none()); assert!(Parser::new("\"\n\"|=3").parse().is_none());
assert!(Parser::new("\"\r\"|=3").parse().is_none()); assert!(Parser::new("\"\r\"|=3").parse().is_none());
} }
#[test]
fn bad_table_names() {
assert!(Parser::new("[]").parse().is_none());
assert!(Parser::new("[.]").parse().is_none());
assert!(Parser::new("[\"\".\"\"]").parse().is_none());
assert!(Parser::new("[a.]").parse().is_none());
assert!(Parser::new("[\"\"]").parse().is_none());
assert!(Parser::new("[!]").parse().is_none());
assert!(Parser::new("[\"\n\"]").parse().is_none());
assert!(Parser::new("[a.b]\n[a.\"b\"]").parse().is_none());
}
#[test]
fn table_names() {
let mut p = Parser::new("
[a.\"b\"]
[\"f f\"]
[\"f.f\"]
[\"\\\"\"]
");
let table = Table(p.parse().unwrap());
assert!(table.lookup("a.b").is_some());
assert!(table.lookup("f f").is_some());
assert!(table.lookup("\"").is_some());
}
} }

View file

@ -5,8 +5,8 @@ use toml::{Parser};
fn run(toml: &str) { fn run(toml: &str) {
let mut p = Parser::new(toml); let mut p = Parser::new(toml);
let table = p.parse(); let table = p.parse();
assert!(p.errors.len() > 0);
assert!(table.is_none()); assert!(table.is_none());
assert!(p.errors.len() > 0);
} }
macro_rules! test( ($name:ident, $toml:expr) => ( macro_rules! test( ($name:ident, $toml:expr) => (

View file

@ -13,7 +13,7 @@ test_string = "You'll hate me after this - #" # " Annoying, isn't it?
harder_test_string = " And when \"'s are in the string, along with # \"" # "and comments are there too" harder_test_string = " And when \"'s are in the string, along with # \"" # "and comments are there too"
# Things will get harder # Things will get harder
[the.hard.bit#] [the.hard."bit#"]
"what?" = "You don't think some user won't do that?" "what?" = "You don't think some user won't do that?"
multi_line_array = [ multi_line_array = [
"]", "]",

View file

@ -1 +1 @@
[valid key] ["valid key"]

View file

@ -1,2 +1,2 @@
[key#group] ["key#group"]
answer = 42 answer = 42