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:
parent
46a70861d5
commit
3536cb50e6
154
src/parser.rs
154
src/parser.rs
|
@ -158,64 +158,56 @@ 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, '[')) => {
|
|
||||||
self.cur.next();
|
|
||||||
let array = self.eat('[');
|
let array = self.eat('[');
|
||||||
|
let start = self.next_pos();
|
||||||
|
|
||||||
// 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 self.eat(']') {
|
||||||
|
if array && !self.expect(']') { return None }
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
if !self.expect('.') { return None }
|
||||||
if section.len() == 0 {
|
|
||||||
self.errors.push(ParserError {
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
if keys.len() == 0 { return None }
|
||||||
|
|
||||||
// Build the section table
|
// Build the section table
|
||||||
let mut table = BTreeMap::new();
|
let mut table = BTreeMap::new();
|
||||||
if !self.values(&mut table) { return None }
|
if !self.values(&mut table) { return None }
|
||||||
if array {
|
if array {
|
||||||
self.insert_array(&mut ret, section, Table(table), start)
|
self.insert_array(&mut ret, &keys[], Table(table), start)
|
||||||
} else {
|
} else {
|
||||||
self.insert_table(&mut ret, section, table, start)
|
self.insert_table(&mut ret, &keys[], table, start)
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
Some(_) => {
|
|
||||||
if !self.values(&mut ret) { return None }
|
if !self.values(&mut ret) { return None }
|
||||||
}
|
}
|
||||||
None if self.errors.len() == 0 => return Some(ret),
|
|
||||||
None => 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('"') {
|
||||||
|
self.finish_string(start, false)
|
||||||
|
} else {
|
||||||
let mut ret = String::new();
|
let mut ret = String::new();
|
||||||
loop {
|
loop {
|
||||||
match self.cur.clone().next() {
|
match self.cur.clone().next() {
|
||||||
|
@ -232,6 +224,19 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(ret)
|
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,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) => (
|
||||||
|
|
|
@ -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 = [
|
||||||
"]",
|
"]",
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
[valid key]
|
["valid key"]
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
[key#group]
|
["key#group"]
|
||||||
answer = 42
|
answer = 42
|
||||||
|
|
Loading…
Reference in a new issue