2014-06-20 19:01:38 -05:00
|
|
|
use std::char;
|
2014-12-21 00:35:14 -06:00
|
|
|
use std::collections::BTreeMap;
|
2014-11-04 13:56:11 -06:00
|
|
|
use std::error::Error;
|
2014-06-20 19:01:38 -05:00
|
|
|
use std::num::FromStrRadix;
|
|
|
|
use std::str;
|
|
|
|
|
2014-12-06 16:51:51 -06:00
|
|
|
use Table as TomlTable;
|
2015-01-03 07:28:22 -06:00
|
|
|
use Value::{self, Array, Table, Float, Integer, Boolean, Datetime};
|
2014-06-20 19:01:38 -05:00
|
|
|
|
2014-06-20 19:30:08 -05:00
|
|
|
/// Parser for converting a string to a TOML `Value` instance.
|
|
|
|
///
|
|
|
|
/// This parser contains the string slice that is being parsed, and exports the
|
|
|
|
/// list of errors which have occurred during parsing.
|
2014-06-20 19:01:38 -05:00
|
|
|
pub struct Parser<'a> {
|
|
|
|
input: &'a str,
|
2014-12-23 10:01:35 -06:00
|
|
|
cur: str::CharIndices<'a>,
|
2014-06-20 19:30:08 -05:00
|
|
|
|
|
|
|
/// A list of all errors which have occurred during parsing.
|
|
|
|
///
|
|
|
|
/// Not all parse errors are fatal, so this list is added to as much as
|
|
|
|
/// possible without aborting parsing. If `None` is returned by `parse`, it
|
|
|
|
/// is guaranteed that this list is not empty.
|
2014-08-17 18:28:44 -05:00
|
|
|
pub errors: Vec<ParserError>,
|
2014-06-20 19:01:38 -05:00
|
|
|
}
|
|
|
|
|
2014-06-20 19:30:08 -05:00
|
|
|
/// A structure representing a parse error.
|
|
|
|
///
|
|
|
|
/// The data in this structure can be used to trace back to the original cause
|
|
|
|
/// of the error in order to provide diagnostics about parse errors.
|
2015-01-03 07:28:22 -06:00
|
|
|
#[derive(Show)]
|
2014-08-17 18:28:44 -05:00
|
|
|
pub struct ParserError {
|
2014-06-20 19:30:08 -05:00
|
|
|
/// The low byte at which this error is pointing at.
|
2015-01-09 13:48:06 -06:00
|
|
|
pub lo: usize,
|
2014-06-20 19:30:08 -05:00
|
|
|
/// One byte beyond the last character at which this error is pointing at.
|
2015-01-09 13:48:06 -06:00
|
|
|
pub hi: usize,
|
2014-06-20 19:30:08 -05:00
|
|
|
/// A human-readable description explaining what the error is.
|
2014-06-20 19:01:38 -05:00
|
|
|
pub desc: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> Parser<'a> {
|
2014-06-20 19:30:08 -05:00
|
|
|
/// Creates a new parser for a string.
|
|
|
|
///
|
|
|
|
/// The parser can be executed by invoking the `parse` method.
|
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// let toml = r#"
|
|
|
|
/// [test]
|
|
|
|
/// foo = "bar"
|
|
|
|
/// "#;
|
|
|
|
///
|
|
|
|
/// let mut parser = toml::Parser::new(toml);
|
|
|
|
/// match parser.parse() {
|
2015-01-07 10:58:50 -06:00
|
|
|
/// Some(value) => println!("found toml: {:?}", value),
|
2014-06-20 19:30:08 -05:00
|
|
|
/// None => {
|
2015-01-07 10:58:50 -06:00
|
|
|
/// println!("parse errors: {:?}", parser.errors);
|
2014-06-20 19:30:08 -05:00
|
|
|
/// }
|
|
|
|
/// }
|
|
|
|
/// ```
|
2014-06-20 19:01:38 -05:00
|
|
|
pub fn new(s: &'a str) -> Parser<'a> {
|
|
|
|
Parser {
|
|
|
|
input: s,
|
|
|
|
cur: s.char_indices(),
|
|
|
|
errors: Vec::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-23 10:58:44 -05:00
|
|
|
/// Converts a byte offset from an error message to a (line, column) pair
|
|
|
|
///
|
|
|
|
/// All indexes are 0-based.
|
2015-01-09 13:48:06 -06:00
|
|
|
pub fn to_linecol(&self, offset: usize) -> (usize, usize) {
|
2014-06-23 10:58:44 -05:00
|
|
|
let mut cur = 0;
|
|
|
|
for (i, line) in self.input.lines().enumerate() {
|
|
|
|
if cur + line.len() > offset {
|
|
|
|
return (i, offset - cur)
|
|
|
|
}
|
|
|
|
cur += line.len() + 1;
|
|
|
|
}
|
|
|
|
return (self.input.lines().count(), 0)
|
|
|
|
}
|
|
|
|
|
2015-01-09 13:48:06 -06:00
|
|
|
fn next_pos(&self) -> usize {
|
2014-12-14 17:14:18 -06:00
|
|
|
self.cur.clone().next().map(|p| p.0).unwrap_or(self.input.len())
|
2014-06-20 19:01:38 -05:00
|
|
|
}
|
|
|
|
|
2014-10-30 15:47:21 -05:00
|
|
|
// Returns true and consumes the next character if it matches `ch`,
|
|
|
|
// otherwise do nothing and return false
|
2014-06-20 19:01:38 -05:00
|
|
|
fn eat(&mut self, ch: char) -> bool {
|
2015-01-15 17:10:07 -06:00
|
|
|
match self.peek(0) {
|
2014-06-20 19:01:38 -05:00
|
|
|
Some((_, c)) if c == ch => { self.cur.next(); true }
|
|
|
|
Some(_) | None => false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-15 17:10:07 -06:00
|
|
|
// Peeks ahead `n` characters
|
|
|
|
fn peek(&self, n: usize) -> Option<(usize, char)> {
|
|
|
|
self.cur.clone().skip(n).next()
|
|
|
|
}
|
|
|
|
|
2014-06-20 19:01:38 -05:00
|
|
|
fn expect(&mut self, ch: char) -> bool {
|
|
|
|
if self.eat(ch) { return true }
|
|
|
|
let mut it = self.cur.clone();
|
2014-12-14 17:14:18 -06:00
|
|
|
let lo = it.next().map(|p| p.0).unwrap_or(self.input.len());
|
|
|
|
let hi = it.next().map(|p| p.0).unwrap_or(self.input.len());
|
2014-08-17 18:28:44 -05:00
|
|
|
self.errors.push(ParserError {
|
2014-06-20 19:01:38 -05:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2015-01-15 17:10:07 -06:00
|
|
|
// Consumes whitespace ('\t' and ' ') until another character (or EOF) is
|
|
|
|
// reached. Returns if any whitespace was consumed
|
|
|
|
fn ws(&mut self) -> bool {
|
|
|
|
let mut ret = false;
|
2014-06-20 19:01:38 -05:00
|
|
|
loop {
|
2015-01-15 17:10:07 -06:00
|
|
|
match self.peek(0) {
|
2014-06-20 19:01:38 -05:00
|
|
|
Some((_, '\t')) |
|
2015-01-15 17:10:07 -06:00
|
|
|
Some((_, ' ')) => { self.cur.next(); ret = true; }
|
2014-06-20 19:01:38 -05:00
|
|
|
_ => break,
|
|
|
|
}
|
|
|
|
}
|
2015-01-15 17:10:07 -06:00
|
|
|
ret
|
2014-06-20 19:01:38 -05:00
|
|
|
}
|
|
|
|
|
2014-10-30 15:47:21 -05:00
|
|
|
// Consumes the rest of the line after a comment character
|
2015-01-15 17:10:07 -06:00
|
|
|
fn comment(&mut self) -> bool {
|
|
|
|
if !self.eat('#') { return false }
|
2014-06-20 19:01:38 -05:00
|
|
|
for (_, ch) in self.cur {
|
|
|
|
if ch == '\n' { break }
|
|
|
|
}
|
2015-01-15 17:10:07 -06:00
|
|
|
true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Consumes a newline if one is next
|
|
|
|
fn newline(&mut self) -> bool {
|
|
|
|
match self.peek(0) {
|
|
|
|
Some((_, '\n')) => { self.cur.next(); true }
|
|
|
|
Some((_, '\r')) if self.peek(1).map(|c| c.1) == Some('\n') => {
|
|
|
|
self.cur.next(); self.cur.next(); true
|
|
|
|
}
|
|
|
|
_ => false
|
|
|
|
}
|
2014-06-20 19:01:38 -05:00
|
|
|
}
|
|
|
|
|
2014-06-20 19:30:08 -05:00
|
|
|
/// Executes the parser, parsing the string contained within.
|
|
|
|
///
|
2014-09-21 09:55:13 -05:00
|
|
|
/// This function will return the `TomlTable` instance if parsing is
|
|
|
|
/// successful, or it will return `None` if any parse error or invalid TOML
|
|
|
|
/// error occurs.
|
2014-06-20 19:30:08 -05:00
|
|
|
///
|
|
|
|
/// If an error occurs, the `errors` field of this parser can be consulted
|
|
|
|
/// to determine the cause of the parse failure.
|
2014-09-21 09:55:13 -05:00
|
|
|
pub fn parse(&mut self) -> Option<TomlTable> {
|
2014-12-21 00:35:14 -06:00
|
|
|
let mut ret = BTreeMap::new();
|
2014-06-20 19:01:38 -05:00
|
|
|
loop {
|
|
|
|
self.ws();
|
2015-01-15 17:10:07 -06:00
|
|
|
if self.newline() { continue }
|
|
|
|
match self.peek(0) {
|
2014-06-20 19:01:38 -05:00
|
|
|
Some((_, '#')) => { self.comment(); }
|
|
|
|
Some((start, '[')) => {
|
|
|
|
self.cur.next();
|
|
|
|
let array = self.eat('[');
|
2014-10-30 15:47:21 -05:00
|
|
|
|
|
|
|
// Parse the name of the section
|
2014-06-20 19:01:38 -05:00
|
|
|
let mut section = String::new();
|
|
|
|
for (pos, ch) in self.cur {
|
|
|
|
if ch == ']' { break }
|
|
|
|
if ch == '[' {
|
2014-08-17 18:28:44 -05:00
|
|
|
self.errors.push(ParserError {
|
2014-06-20 19:01:38 -05:00
|
|
|
lo: pos,
|
|
|
|
hi: pos + 1,
|
|
|
|
desc: format!("section names cannot contain \
|
|
|
|
a `[` character"),
|
|
|
|
});
|
|
|
|
continue
|
|
|
|
}
|
2014-10-11 12:04:44 -05:00
|
|
|
section.push(ch);
|
2014-06-20 19:01:38 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if section.len() == 0 {
|
2014-08-17 18:28:44 -05:00
|
|
|
self.errors.push(ParserError {
|
2014-06-20 19:01:38 -05:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2014-10-30 15:47:21 -05:00
|
|
|
// Build the section table
|
2014-12-21 00:35:14 -06:00
|
|
|
let mut table = BTreeMap::new();
|
2014-06-20 19:01:38 -05:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-30 15:47:21 -05:00
|
|
|
// Parses the values into the given TomlTable. Returns true in case of success
|
|
|
|
// and false in case of error.
|
2014-09-21 09:55:13 -05:00
|
|
|
fn values(&mut self, into: &mut TomlTable) -> bool {
|
2014-06-20 19:01:38 -05:00
|
|
|
loop {
|
|
|
|
self.ws();
|
2015-01-15 17:10:07 -06:00
|
|
|
if self.newline() { continue }
|
2014-06-20 19:01:38 -05:00
|
|
|
match self.cur.clone().next() {
|
2015-01-15 17:10:07 -06:00
|
|
|
Some((_, '#')) => { self.comment(); }
|
2014-06-20 19:01:38 -05:00
|
|
|
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' => {
|
2014-08-17 18:28:44 -05:00
|
|
|
self.errors.push(ParserError {
|
2014-06-20 19:01:38 -05:00
|
|
|
lo: start,
|
|
|
|
hi: pos + 1,
|
|
|
|
desc: format!("keys cannot be defined \
|
|
|
|
across lines"),
|
|
|
|
})
|
|
|
|
}
|
2014-10-11 12:04:44 -05:00
|
|
|
c => key.push(c),
|
2014-06-20 19:01:38 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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();
|
2014-06-24 00:25:54 -05:00
|
|
|
self.eat('\r');
|
2014-06-20 19:01:38 -05:00
|
|
|
self.eat('\n');
|
|
|
|
}
|
|
|
|
None => break,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2014-10-30 15:47:21 -05:00
|
|
|
// Parses a value
|
2014-06-20 19:01:38 -05:00
|
|
|
fn value(&mut self) -> Option<Value> {
|
|
|
|
self.ws();
|
|
|
|
match self.cur.clone().next() {
|
|
|
|
Some((pos, '"')) => self.string(pos),
|
2014-07-15 21:00:50 -05:00
|
|
|
Some((pos, '\'')) => self.literal_string(pos),
|
2014-06-20 19:01:38 -05:00
|
|
|
Some((pos, 't')) |
|
|
|
|
Some((pos, 'f')) => self.boolean(pos),
|
|
|
|
Some((pos, '[')) => self.array(pos),
|
2015-01-15 16:27:55 -06:00
|
|
|
Some((pos, '-')) |
|
|
|
|
Some((pos, '+')) => self.number_or_datetime(pos),
|
2014-11-22 07:20:55 -06:00
|
|
|
Some((pos, ch)) if ch.is_digit(10) => self.number_or_datetime(pos),
|
2014-06-20 19:01:38 -05:00
|
|
|
_ => {
|
|
|
|
let mut it = self.cur.clone();
|
2014-12-14 17:14:18 -06:00
|
|
|
let lo = it.next().map(|p| p.0).unwrap_or(self.input.len());
|
|
|
|
let hi = it.next().map(|p| p.0).unwrap_or(self.input.len());
|
2014-08-17 18:28:44 -05:00
|
|
|
self.errors.push(ParserError {
|
2014-06-20 19:01:38 -05:00
|
|
|
lo: lo,
|
|
|
|
hi: hi,
|
|
|
|
desc: format!("expected a value"),
|
|
|
|
});
|
|
|
|
return None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-30 15:47:21 -05:00
|
|
|
// Parses a single or multi-line string
|
2015-01-09 13:48:06 -06:00
|
|
|
fn string(&mut self, start: usize) -> Option<Value> {
|
2014-06-20 19:01:38 -05:00
|
|
|
if !self.expect('"') { return None }
|
2014-07-15 21:00:50 -05:00
|
|
|
let mut multiline = false;
|
2014-06-20 19:01:38 -05:00
|
|
|
let mut ret = String::new();
|
|
|
|
|
2014-07-18 05:19:21 -05:00
|
|
|
// detect multiline literals, but be careful about empty ""
|
|
|
|
// strings
|
2014-07-15 21:00:50 -05:00
|
|
|
if self.eat('"') {
|
2014-07-18 05:19:21 -05:00
|
|
|
if self.eat('"') {
|
|
|
|
multiline = true;
|
2015-01-15 17:10:07 -06:00
|
|
|
self.newline();
|
2014-07-18 05:19:21 -05:00
|
|
|
} else {
|
|
|
|
// empty
|
2014-11-18 02:13:21 -06:00
|
|
|
return Some(Value::String(ret))
|
2014-07-18 05:19:21 -05:00
|
|
|
}
|
2014-07-15 21:00:50 -05:00
|
|
|
}
|
|
|
|
|
2014-06-20 19:01:38 -05:00
|
|
|
loop {
|
2015-01-15 17:10:07 -06:00
|
|
|
while self.newline() { ret.push('\n') }
|
2014-06-20 19:01:38 -05:00
|
|
|
match self.cur.next() {
|
2014-07-15 21:00:50 -05:00
|
|
|
Some((_, '"')) => {
|
|
|
|
if multiline {
|
|
|
|
if !self.eat('"') { ret.push_str("\""); continue }
|
|
|
|
if !self.eat('"') { ret.push_str("\"\""); continue }
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
2014-06-20 19:01:38 -05:00
|
|
|
Some((pos, '\\')) => {
|
2014-07-15 21:00:50 -05:00
|
|
|
match escape(self, pos, multiline) {
|
2014-10-11 12:04:44 -05:00
|
|
|
Some(c) => ret.push(c),
|
2014-06-20 19:01:38 -05:00
|
|
|
None => {}
|
|
|
|
}
|
|
|
|
}
|
2014-12-12 15:19:22 -06:00
|
|
|
Some((pos, ch)) if ch < '\u{1f}' => {
|
2014-06-20 19:01:38 -05:00
|
|
|
let mut escaped = String::new();
|
2014-11-22 07:20:55 -06:00
|
|
|
for c in ch.escape_default() {
|
|
|
|
escaped.push(c);
|
|
|
|
}
|
2014-08-17 18:28:44 -05:00
|
|
|
self.errors.push(ParserError {
|
2014-06-20 19:01:38 -05:00
|
|
|
lo: pos,
|
|
|
|
hi: pos + 1,
|
|
|
|
desc: format!("control character `{}` must be escaped",
|
|
|
|
escaped)
|
|
|
|
});
|
|
|
|
}
|
2014-10-11 12:04:44 -05:00
|
|
|
Some((_, ch)) => ret.push(ch),
|
2014-06-20 19:01:38 -05:00
|
|
|
None => {
|
2014-08-17 18:28:44 -05:00
|
|
|
self.errors.push(ParserError {
|
2014-06-20 19:01:38 -05:00
|
|
|
lo: start,
|
|
|
|
hi: self.input.len(),
|
|
|
|
desc: format!("unterminated string literal"),
|
|
|
|
});
|
|
|
|
return None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-18 02:13:21 -06:00
|
|
|
return Some(Value::String(ret));
|
2014-06-20 19:01:38 -05:00
|
|
|
|
2015-01-09 13:48:06 -06:00
|
|
|
fn escape(me: &mut Parser, pos: usize, multiline: bool) -> Option<char> {
|
2015-01-15 17:10:07 -06:00
|
|
|
if multiline && me.newline() {
|
|
|
|
while me.ws() || me.newline() { /* ... */ }
|
|
|
|
return None
|
|
|
|
}
|
2014-06-20 19:01:38 -05:00
|
|
|
match me.cur.next() {
|
2014-12-12 15:19:22 -06:00
|
|
|
Some((_, 'b')) => Some('\u{8}'),
|
|
|
|
Some((_, 't')) => Some('\u{9}'),
|
|
|
|
Some((_, 'n')) => Some('\u{a}'),
|
|
|
|
Some((_, 'f')) => Some('\u{c}'),
|
|
|
|
Some((_, 'r')) => Some('\u{d}'),
|
|
|
|
Some((_, '"')) => Some('\u{22}'),
|
|
|
|
Some((_, '/')) => Some('\u{2f}'),
|
|
|
|
Some((_, '\\')) => Some('\u{5c}'),
|
2014-07-15 20:36:39 -05:00
|
|
|
Some((pos, c @ 'u')) |
|
|
|
|
Some((pos, c @ 'U')) => {
|
|
|
|
let len = if c == 'u' {4} else {8};
|
|
|
|
let num = if me.input.is_char_boundary(pos + 1 + len) {
|
|
|
|
me.input.slice(pos + 1, pos + 1 + len)
|
2014-06-20 19:01:38 -05:00
|
|
|
} else {
|
|
|
|
"invalid"
|
|
|
|
};
|
|
|
|
match FromStrRadix::from_str_radix(num, 16) {
|
|
|
|
Some(n) => {
|
|
|
|
match char::from_u32(n) {
|
|
|
|
Some(c) => {
|
2014-07-15 20:36:39 -05:00
|
|
|
for _ in range(0, len) {
|
|
|
|
me.cur.next();
|
|
|
|
}
|
2014-06-20 19:01:38 -05:00
|
|
|
return Some(c)
|
|
|
|
}
|
|
|
|
None => {
|
2014-08-17 18:28:44 -05:00
|
|
|
me.errors.push(ParserError {
|
2014-06-20 19:01:38 -05:00
|
|
|
lo: pos + 1,
|
|
|
|
hi: pos + 5,
|
|
|
|
desc: format!("codepoint `{:x}` is \
|
|
|
|
not a valid unicode \
|
|
|
|
codepoint", n),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None => {
|
2014-08-17 18:28:44 -05:00
|
|
|
me.errors.push(ParserError {
|
2014-06-20 19:01:38 -05:00
|
|
|
lo: pos,
|
|
|
|
hi: pos + 1,
|
2014-07-15 20:36:39 -05:00
|
|
|
desc: format!("expected {} hex digits \
|
2014-07-15 21:13:42 -05:00
|
|
|
after a `{}` escape", len, c),
|
2014-06-20 19:01:38 -05:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
Some((pos, ch)) => {
|
|
|
|
let mut escaped = String::new();
|
2014-11-22 07:20:55 -06:00
|
|
|
for c in ch.escape_default() {
|
|
|
|
escaped.push(c);
|
|
|
|
}
|
2014-06-20 19:01:38 -05:00
|
|
|
let next_pos = me.next_pos();
|
2014-08-17 18:28:44 -05:00
|
|
|
me.errors.push(ParserError {
|
2014-06-20 19:01:38 -05:00
|
|
|
lo: pos,
|
|
|
|
hi: next_pos,
|
|
|
|
desc: format!("unknown string escape: `{}`",
|
|
|
|
escaped),
|
|
|
|
});
|
|
|
|
None
|
|
|
|
}
|
|
|
|
None => {
|
2014-08-17 18:28:44 -05:00
|
|
|
me.errors.push(ParserError {
|
2014-06-20 19:01:38 -05:00
|
|
|
lo: pos,
|
|
|
|
hi: pos + 1,
|
|
|
|
desc: format!("unterminated escape sequence"),
|
|
|
|
});
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-09 13:48:06 -06:00
|
|
|
fn literal_string(&mut self, start: usize) -> Option<Value> {
|
2014-07-15 21:00:50 -05:00
|
|
|
if !self.expect('\'') { return None }
|
|
|
|
let mut multiline = false;
|
|
|
|
let mut ret = String::new();
|
|
|
|
|
|
|
|
// detect multiline literals
|
|
|
|
if self.eat('\'') {
|
|
|
|
multiline = true;
|
|
|
|
if !self.expect('\'') { return None }
|
2015-01-15 17:10:07 -06:00
|
|
|
self.newline();
|
2014-07-15 21:00:50 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
loop {
|
|
|
|
match self.cur.next() {
|
|
|
|
Some((_, '\'')) => {
|
|
|
|
if multiline {
|
|
|
|
if !self.eat('\'') { ret.push_str("'"); continue }
|
|
|
|
if !self.eat('\'') { ret.push_str("''"); continue }
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
2014-10-11 12:04:44 -05:00
|
|
|
Some((_, ch)) => ret.push(ch),
|
2014-07-15 21:00:50 -05:00
|
|
|
None => {
|
2014-08-17 18:28:44 -05:00
|
|
|
self.errors.push(ParserError {
|
2014-07-15 21:00:50 -05:00
|
|
|
lo: start,
|
|
|
|
hi: self.input.len(),
|
|
|
|
desc: format!("unterminated string literal"),
|
|
|
|
});
|
|
|
|
return None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-18 02:13:21 -06:00
|
|
|
return Some(Value::String(ret));
|
2014-07-15 21:00:50 -05:00
|
|
|
}
|
|
|
|
|
2015-01-15 16:27:55 -06:00
|
|
|
fn number_or_datetime(&mut self, mut start: usize) -> Option<Value> {
|
|
|
|
let sign = if self.eat('+') { start += 1; true } else {self.eat('-')};
|
2014-06-20 19:01:38 -05:00
|
|
|
let mut is_float = false;
|
|
|
|
loop {
|
|
|
|
match self.cur.clone().next() {
|
2014-11-22 07:20:55 -06:00
|
|
|
Some((_, ch)) if ch.is_digit(10) => { self.cur.next(); }
|
2014-06-20 19:01:38 -05:00
|
|
|
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 {
|
2014-12-23 10:01:35 -06:00
|
|
|
self.input.slice(start, end).parse().map(Float)
|
2014-06-20 19:01:38 -05:00
|
|
|
}
|
2015-01-15 16:27:55 -06:00
|
|
|
} else if !sign && self.eat('-') {
|
2014-06-20 19:01:38 -05:00
|
|
|
self.datetime(start, end + 1)
|
|
|
|
} else {
|
2014-12-23 10:01:35 -06:00
|
|
|
self.input.slice(start, end).parse().map(Integer)
|
2014-06-20 19:01:38 -05:00
|
|
|
};
|
|
|
|
if ret.is_none() {
|
2014-08-17 18:28:44 -05:00
|
|
|
self.errors.push(ParserError {
|
2014-06-20 19:01:38 -05:00
|
|
|
lo: start,
|
|
|
|
hi: end,
|
|
|
|
desc: format!("invalid numeric literal"),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2015-01-09 13:48:06 -06:00
|
|
|
fn boolean(&mut self, start: usize) -> Option<Value> {
|
2014-06-20 19:01:38 -05:00
|
|
|
let rest = self.input.slice_from(start);
|
|
|
|
if rest.starts_with("true") {
|
2015-01-09 13:48:06 -06:00
|
|
|
for _ in 0..4 {
|
2014-06-20 19:01:38 -05:00
|
|
|
self.cur.next();
|
|
|
|
}
|
|
|
|
Some(Boolean(true))
|
|
|
|
} else if rest.starts_with("false") {
|
2015-01-09 13:48:06 -06:00
|
|
|
for _ in 0..5 {
|
2014-06-20 19:01:38 -05:00
|
|
|
self.cur.next();
|
|
|
|
}
|
|
|
|
Some(Boolean(false))
|
|
|
|
} else {
|
|
|
|
let next = self.next_pos();
|
2014-08-17 18:28:44 -05:00
|
|
|
self.errors.push(ParserError {
|
2014-06-20 19:01:38 -05:00
|
|
|
lo: start,
|
|
|
|
hi: next,
|
|
|
|
desc: format!("unexpected character: `{}`",
|
|
|
|
rest.char_at(0)),
|
|
|
|
});
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-09 13:48:06 -06:00
|
|
|
fn datetime(&mut self, start: usize, end_so_far: usize) -> Option<Value> {
|
2014-06-20 19:01:38 -05:00
|
|
|
let mut date = self.input.slice(start, end_so_far).to_string();
|
2015-01-09 13:48:06 -06:00
|
|
|
for _ in 0..15 {
|
2014-06-20 19:01:38 -05:00
|
|
|
match self.cur.next() {
|
2014-10-11 12:04:44 -05:00
|
|
|
Some((_, ch)) => date.push(ch),
|
2014-06-20 19:01:38 -05:00
|
|
|
None => {
|
2014-08-17 18:28:44 -05:00
|
|
|
self.errors.push(ParserError {
|
2014-06-20 19:01:38 -05:00
|
|
|
lo: start,
|
|
|
|
hi: end_so_far,
|
|
|
|
desc: format!("malformed date literal"),
|
|
|
|
});
|
|
|
|
return None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let mut it = date.as_slice().chars();
|
|
|
|
let mut valid = true;
|
2014-11-22 07:20:55 -06:00
|
|
|
valid = valid && it.next().map(|c| c.is_digit(10)).unwrap_or(false);
|
|
|
|
valid = valid && it.next().map(|c| c.is_digit(10)).unwrap_or(false);
|
|
|
|
valid = valid && it.next().map(|c| c.is_digit(10)).unwrap_or(false);
|
|
|
|
valid = valid && it.next().map(|c| c.is_digit(10)).unwrap_or(false);
|
2014-06-20 19:01:38 -05:00
|
|
|
valid = valid && it.next().map(|c| c == '-').unwrap_or(false);
|
2014-11-22 07:20:55 -06:00
|
|
|
valid = valid && it.next().map(|c| c.is_digit(10)).unwrap_or(false);
|
|
|
|
valid = valid && it.next().map(|c| c.is_digit(10)).unwrap_or(false);
|
2014-06-20 19:01:38 -05:00
|
|
|
valid = valid && it.next().map(|c| c == '-').unwrap_or(false);
|
2014-11-22 07:20:55 -06:00
|
|
|
valid = valid && it.next().map(|c| c.is_digit(10)).unwrap_or(false);
|
|
|
|
valid = valid && it.next().map(|c| c.is_digit(10)).unwrap_or(false);
|
2014-06-20 19:01:38 -05:00
|
|
|
valid = valid && it.next().map(|c| c == 'T').unwrap_or(false);
|
2014-11-22 07:20:55 -06:00
|
|
|
valid = valid && it.next().map(|c| c.is_digit(10)).unwrap_or(false);
|
|
|
|
valid = valid && it.next().map(|c| c.is_digit(10)).unwrap_or(false);
|
2014-06-20 19:01:38 -05:00
|
|
|
valid = valid && it.next().map(|c| c == ':').unwrap_or(false);
|
2014-11-22 07:20:55 -06:00
|
|
|
valid = valid && it.next().map(|c| c.is_digit(10)).unwrap_or(false);
|
|
|
|
valid = valid && it.next().map(|c| c.is_digit(10)).unwrap_or(false);
|
2014-06-20 19:01:38 -05:00
|
|
|
valid = valid && it.next().map(|c| c == ':').unwrap_or(false);
|
2014-11-22 07:20:55 -06:00
|
|
|
valid = valid && it.next().map(|c| c.is_digit(10)).unwrap_or(false);
|
|
|
|
valid = valid && it.next().map(|c| c.is_digit(10)).unwrap_or(false);
|
2014-06-20 19:01:38 -05:00
|
|
|
valid = valid && it.next().map(|c| c == 'Z').unwrap_or(false);
|
|
|
|
if valid {
|
|
|
|
Some(Datetime(date.clone()))
|
|
|
|
} else {
|
2014-08-17 18:28:44 -05:00
|
|
|
self.errors.push(ParserError {
|
2014-06-20 19:01:38 -05:00
|
|
|
lo: start,
|
|
|
|
hi: start + date.len(),
|
|
|
|
desc: format!("malformed date literal"),
|
|
|
|
});
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-09 13:48:06 -06:00
|
|
|
fn array(&mut self, _start: usize) -> Option<Value> {
|
2014-06-20 19:01:38 -05:00
|
|
|
if !self.expect('[') { return None }
|
|
|
|
let mut ret = Vec::new();
|
|
|
|
fn consume(me: &mut Parser) {
|
|
|
|
loop {
|
|
|
|
me.ws();
|
2015-01-15 17:10:07 -06:00
|
|
|
if !me.newline() && !me.comment() { break }
|
2014-06-20 19:01:38 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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 {
|
2014-08-17 18:28:44 -05:00
|
|
|
self.errors.push(ParserError {
|
2014-06-20 19:01:38 -05:00
|
|
|
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))
|
|
|
|
}
|
|
|
|
|
2014-09-21 09:55:13 -05:00
|
|
|
fn insert(&mut self, into: &mut TomlTable, key: String, value: Value,
|
2015-01-09 13:48:06 -06:00
|
|
|
key_lo: usize) {
|
2014-06-20 19:01:38 -05:00
|
|
|
if into.contains_key(&key) {
|
2014-08-17 18:28:44 -05:00
|
|
|
self.errors.push(ParserError {
|
2014-06-20 19:01:38 -05:00
|
|
|
lo: key_lo,
|
|
|
|
hi: key_lo + key.len(),
|
|
|
|
desc: format!("duplicate key: `{}`", key),
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
into.insert(key, value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-14 17:14:18 -06:00
|
|
|
fn recurse<'b>(&mut self, mut cur: &'b mut TomlTable, orig_key: &'b str,
|
2015-01-09 13:48:06 -06:00
|
|
|
key_lo: usize) -> Option<(&'b mut TomlTable, &'b str)> {
|
2014-06-20 19:01:38 -05:00
|
|
|
if orig_key.starts_with(".") || orig_key.ends_with(".") ||
|
|
|
|
orig_key.contains("..") {
|
2014-08-17 18:28:44 -05:00
|
|
|
self.errors.push(ParserError {
|
2014-06-20 19:01:38 -05:00
|
|
|
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) {
|
2014-11-07 00:11:52 -06:00
|
|
|
match *tmp.get_mut(&part).unwrap() {
|
2014-06-20 19:01:38 -05:00
|
|
|
Table(ref mut table) => {
|
|
|
|
cur = table;
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
Array(ref mut array) => {
|
2014-09-17 02:00:15 -05:00
|
|
|
match array.as_mut_slice().last_mut() {
|
2015-01-07 10:58:50 -06:00
|
|
|
Some(&mut Table(ref mut table)) => cur = table,
|
2014-06-20 19:01:38 -05:00
|
|
|
_ => {
|
2014-08-17 18:28:44 -05:00
|
|
|
self.errors.push(ParserError {
|
2014-06-20 19:01:38 -05:00
|
|
|
lo: key_lo,
|
|
|
|
hi: key_lo + key.len(),
|
|
|
|
desc: format!("array `{}` does not contain \
|
|
|
|
tables", part)
|
|
|
|
});
|
|
|
|
return None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
_ => {
|
2014-08-17 18:28:44 -05:00
|
|
|
self.errors.push(ParserError {
|
2014-06-20 19:01:38 -05:00
|
|
|
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
|
2014-12-21 00:35:14 -06:00
|
|
|
tmp.insert(part.clone(), Table(BTreeMap::new()));
|
2014-11-07 00:11:52 -06:00
|
|
|
match *tmp.get_mut(&part).unwrap() {
|
2014-06-20 19:01:38 -05:00
|
|
|
Table(ref mut inner) => cur = inner,
|
|
|
|
_ => unreachable!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Some((cur, orig_key.slice_from(key.len() + 1)))
|
|
|
|
}
|
|
|
|
|
2014-09-21 09:55:13 -05:00
|
|
|
fn insert_table(&mut self, into: &mut TomlTable, key: String, value: TomlTable,
|
2015-01-09 13:48:06 -06:00
|
|
|
key_lo: usize) {
|
2014-06-20 19:01:38 -05:00
|
|
|
let (into, key) = match self.recurse(into, key.as_slice(), key_lo) {
|
|
|
|
Some(pair) => pair,
|
|
|
|
None => return,
|
|
|
|
};
|
|
|
|
let key = key.to_string();
|
2014-12-12 00:29:27 -06:00
|
|
|
let mut added = false;
|
2014-06-20 19:01:38 -05:00
|
|
|
if !into.contains_key(&key) {
|
2014-12-21 00:35:14 -06:00
|
|
|
into.insert(key.clone(), Table(BTreeMap::new()));
|
2014-12-12 00:29:27 -06:00
|
|
|
added = true;
|
2014-06-20 19:01:38 -05:00
|
|
|
}
|
2014-11-07 00:11:52 -06:00
|
|
|
match into.get_mut(&key) {
|
2015-01-07 10:58:50 -06:00
|
|
|
Some(&mut Table(ref mut table)) => {
|
2014-12-12 00:29:27 -06:00
|
|
|
let any_tables = table.values().any(|v| v.as_table().is_some());
|
|
|
|
if !any_tables && !added {
|
|
|
|
self.errors.push(ParserError {
|
|
|
|
lo: key_lo,
|
|
|
|
hi: key_lo + key.len(),
|
|
|
|
desc: format!("redefinition of table `{}`", key),
|
|
|
|
});
|
|
|
|
}
|
2014-09-17 02:00:15 -05:00
|
|
|
for (k, v) in value.into_iter() {
|
2014-11-07 00:11:52 -06:00
|
|
|
if table.insert(k.clone(), v).is_some() {
|
2014-08-17 18:28:44 -05:00
|
|
|
self.errors.push(ParserError {
|
2014-06-20 19:01:38 -05:00
|
|
|
lo: key_lo,
|
|
|
|
hi: key_lo + key.len(),
|
|
|
|
desc: format!("duplicate key `{}` in table", k),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Some(_) => {
|
2014-08-17 18:28:44 -05:00
|
|
|
self.errors.push(ParserError {
|
2014-06-20 19:01:38 -05:00
|
|
|
lo: key_lo,
|
|
|
|
hi: key_lo + key.len(),
|
|
|
|
desc: format!("duplicate key `{}` in table", key),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
None => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-21 09:55:13 -05:00
|
|
|
fn insert_array(&mut self, into: &mut TomlTable, key: String, value: Value,
|
2015-01-09 13:48:06 -06:00
|
|
|
key_lo: usize) {
|
2014-06-20 19:01:38 -05:00
|
|
|
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()));
|
|
|
|
}
|
2014-11-07 00:11:52 -06:00
|
|
|
match *into.get_mut(&key).unwrap() {
|
2014-06-20 19:01:38 -05:00
|
|
|
Array(ref mut vec) => {
|
2015-01-01 10:48:47 -06:00
|
|
|
match vec.as_slice().first() {
|
2014-06-20 19:01:38 -05:00
|
|
|
Some(ref v) if !v.same_type(&value) => {
|
2014-08-17 18:28:44 -05:00
|
|
|
self.errors.push(ParserError {
|
2014-06-20 19:01:38 -05:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
_ => {
|
2014-08-17 18:28:44 -05:00
|
|
|
self.errors.push(ParserError {
|
2014-06-20 19:01:38 -05:00
|
|
|
lo: key_lo,
|
|
|
|
hi: key_lo + key.len(),
|
|
|
|
desc: format!("key `{}` was previously not an array", key),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-06-23 10:58:44 -05:00
|
|
|
|
2014-11-04 13:56:11 -06:00
|
|
|
impl Error for ParserError {
|
|
|
|
fn description(&self) -> &str { "TOML parse error" }
|
|
|
|
fn detail(&self) -> Option<String> { Some(self.desc.clone()) }
|
|
|
|
}
|
|
|
|
|
2014-06-23 10:58:44 -05:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2014-11-18 02:13:21 -06:00
|
|
|
use Value::Table;
|
|
|
|
use Parser;
|
2014-06-23 10:58:44 -05:00
|
|
|
|
2014-06-25 09:00:10 -05:00
|
|
|
#[test]
|
|
|
|
fn crlf() {
|
|
|
|
let mut p = Parser::new("\
|
|
|
|
[project]\r\n\
|
|
|
|
\r\n\
|
|
|
|
name = \"splay\"\r\n\
|
|
|
|
version = \"0.1.0\"\r\n\
|
|
|
|
authors = [\"alex@crichton.co\"]\r\n\
|
|
|
|
\r\n\
|
|
|
|
[[lib]]\r\n\
|
|
|
|
\r\n\
|
|
|
|
path = \"lib.rs\"\r\n\
|
|
|
|
name = \"splay\"\r\n\
|
2014-10-30 15:47:21 -05:00
|
|
|
description = \"\"\"\
|
|
|
|
A Rust implementation of a TAR file reader and writer. This library does not\r\n\
|
|
|
|
currently handle compression, but it is abstract over all I/O readers and\r\n\
|
|
|
|
writers. Additionally, great lengths are taken to ensure that the entire\r\n\
|
|
|
|
contents are never required to be entirely resident in memory all at once.\r\n\
|
|
|
|
\"\"\"\
|
2014-06-25 09:00:10 -05:00
|
|
|
");
|
|
|
|
assert!(p.parse().is_some());
|
|
|
|
}
|
|
|
|
|
2014-06-23 10:58:44 -05:00
|
|
|
#[test]
|
|
|
|
fn linecol() {
|
|
|
|
let p = Parser::new("ab\ncde\nf");
|
|
|
|
assert_eq!(p.to_linecol(0), (0, 0));
|
|
|
|
assert_eq!(p.to_linecol(1), (0, 1));
|
|
|
|
assert_eq!(p.to_linecol(3), (1, 0));
|
|
|
|
assert_eq!(p.to_linecol(4), (1, 1));
|
|
|
|
assert_eq!(p.to_linecol(7), (2, 0));
|
|
|
|
}
|
|
|
|
|
2014-07-15 20:36:39 -05:00
|
|
|
#[test]
|
|
|
|
fn fun_with_strings() {
|
|
|
|
let mut p = Parser::new(r#"
|
|
|
|
bar = "\U00000000"
|
2014-07-15 21:00:50 -05:00
|
|
|
key1 = "One\nTwo"
|
|
|
|
key2 = """One\nTwo"""
|
|
|
|
key3 = """
|
|
|
|
One
|
|
|
|
Two"""
|
|
|
|
|
|
|
|
key4 = "The quick brown fox jumps over the lazy dog."
|
|
|
|
key5 = """
|
|
|
|
The quick brown \
|
|
|
|
|
|
|
|
|
|
|
|
fox jumps over \
|
|
|
|
the lazy dog."""
|
|
|
|
key6 = """\
|
|
|
|
The quick brown \
|
|
|
|
fox jumps over \
|
|
|
|
the lazy dog.\
|
|
|
|
"""
|
|
|
|
# What you see is what you get.
|
|
|
|
winpath = 'C:\Users\nodejs\templates'
|
|
|
|
winpath2 = '\\ServerX\admin$\system32\'
|
|
|
|
quoted = 'Tom "Dubs" Preston-Werner'
|
|
|
|
regex = '<\i\c*\s*>'
|
|
|
|
|
|
|
|
regex2 = '''I [dw]on't need \d{2} apples'''
|
|
|
|
lines = '''
|
|
|
|
The first newline is
|
|
|
|
trimmed in raw strings.
|
|
|
|
All other whitespace
|
|
|
|
is preserved.
|
|
|
|
'''
|
2014-07-15 20:36:39 -05:00
|
|
|
"#);
|
|
|
|
let table = Table(p.parse().unwrap());
|
2014-07-15 21:00:50 -05:00
|
|
|
assert_eq!(table.lookup("bar").and_then(|k| k.as_str()), Some("\0"));
|
|
|
|
assert_eq!(table.lookup("key1").and_then(|k| k.as_str()),
|
|
|
|
Some("One\nTwo"));
|
|
|
|
assert_eq!(table.lookup("key2").and_then(|k| k.as_str()),
|
|
|
|
Some("One\nTwo"));
|
|
|
|
assert_eq!(table.lookup("key3").and_then(|k| k.as_str()),
|
|
|
|
Some("One\nTwo"));
|
|
|
|
|
|
|
|
let msg = "The quick brown fox jumps over the lazy dog.";
|
|
|
|
assert_eq!(table.lookup("key4").and_then(|k| k.as_str()), Some(msg));
|
|
|
|
assert_eq!(table.lookup("key5").and_then(|k| k.as_str()), Some(msg));
|
|
|
|
assert_eq!(table.lookup("key6").and_then(|k| k.as_str()), Some(msg));
|
|
|
|
|
|
|
|
assert_eq!(table.lookup("winpath").and_then(|k| k.as_str()),
|
|
|
|
Some(r"C:\Users\nodejs\templates"));
|
|
|
|
assert_eq!(table.lookup("winpath2").and_then(|k| k.as_str()),
|
|
|
|
Some(r"\\ServerX\admin$\system32\"));
|
|
|
|
assert_eq!(table.lookup("quoted").and_then(|k| k.as_str()),
|
|
|
|
Some(r#"Tom "Dubs" Preston-Werner"#));
|
|
|
|
assert_eq!(table.lookup("regex").and_then(|k| k.as_str()),
|
|
|
|
Some(r"<\i\c*\s*>"));
|
|
|
|
assert_eq!(table.lookup("regex2").and_then(|k| k.as_str()),
|
|
|
|
Some(r"I [dw]on't need \d{2} apples"));
|
|
|
|
assert_eq!(table.lookup("lines").and_then(|k| k.as_str()),
|
|
|
|
Some("The first newline is\n\
|
|
|
|
trimmed in raw strings.\n \
|
|
|
|
All other whitespace\n \
|
|
|
|
is preserved.\n"));
|
2014-07-15 20:36:39 -05:00
|
|
|
}
|
2014-12-12 00:29:27 -06:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn tables_in_arrays() {
|
|
|
|
let mut p = Parser::new(r#"
|
|
|
|
[[foo]]
|
|
|
|
#…
|
|
|
|
[foo.bar]
|
|
|
|
#…
|
|
|
|
|
|
|
|
[[foo]]
|
|
|
|
#…
|
|
|
|
[foo.bar]
|
|
|
|
#...
|
|
|
|
"#);
|
|
|
|
let table = Table(p.parse().unwrap());
|
|
|
|
table.lookup("foo.0.bar").unwrap().as_table().unwrap();
|
|
|
|
table.lookup("foo.1.bar").unwrap().as_table().unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn fruit() {
|
|
|
|
let mut p = Parser::new(r#"
|
|
|
|
[[fruit]]
|
|
|
|
name = "apple"
|
|
|
|
|
|
|
|
[fruit.physical]
|
|
|
|
color = "red"
|
|
|
|
shape = "round"
|
|
|
|
|
|
|
|
[[fruit.variety]]
|
|
|
|
name = "red delicious"
|
|
|
|
|
|
|
|
[[fruit.variety]]
|
|
|
|
name = "granny smith"
|
|
|
|
|
|
|
|
[[fruit]]
|
|
|
|
name = "banana"
|
|
|
|
|
|
|
|
[[fruit.variety]]
|
|
|
|
name = "plantain"
|
|
|
|
"#);
|
|
|
|
let table = Table(p.parse().unwrap());
|
|
|
|
assert_eq!(table.lookup("fruit.0.name").and_then(|k| k.as_str()),
|
|
|
|
Some("apple"));
|
|
|
|
assert_eq!(table.lookup("fruit.0.physical.color").and_then(|k| k.as_str()),
|
|
|
|
Some("red"));
|
|
|
|
assert_eq!(table.lookup("fruit.0.physical.shape").and_then(|k| k.as_str()),
|
|
|
|
Some("round"));
|
|
|
|
assert_eq!(table.lookup("fruit.0.variety.0.name").and_then(|k| k.as_str()),
|
|
|
|
Some("red delicious"));
|
|
|
|
assert_eq!(table.lookup("fruit.0.variety.1.name").and_then(|k| k.as_str()),
|
|
|
|
Some("granny smith"));
|
|
|
|
assert_eq!(table.lookup("fruit.1.name").and_then(|k| k.as_str()),
|
|
|
|
Some("banana"));
|
|
|
|
assert_eq!(table.lookup("fruit.1.variety.0.name").and_then(|k| k.as_str()),
|
|
|
|
Some("plantain"));
|
|
|
|
}
|
2015-01-15 17:10:07 -06:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn stray_cr() {
|
|
|
|
assert!(Parser::new("\r").parse().is_none());
|
|
|
|
assert!(Parser::new("a = [ \r ]").parse().is_none());
|
|
|
|
assert!(Parser::new("a = \"\"\"\r\"\"\"").parse().is_none());
|
|
|
|
assert!(Parser::new("a = \"\"\"\\ \r \"\"\"").parse().is_none());
|
|
|
|
|
|
|
|
let mut p = Parser::new("foo = '''\r'''");
|
|
|
|
let table = Table(p.parse().unwrap());
|
|
|
|
assert_eq!(table.lookup("foo").and_then(|k| k.as_str()), Some("\r"));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn many_blank() {
|
|
|
|
let mut p = Parser::new("foo = \"\"\"\n\n\n\"\"\"");
|
|
|
|
let table = Table(p.parse().unwrap());
|
|
|
|
assert_eq!(table.lookup("foo").and_then(|k| k.as_str()), Some("\n\n"));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn literal_eats_crlf() {
|
|
|
|
let mut p = Parser::new("
|
|
|
|
foo = \"\"\"\\\r\n\"\"\"
|
|
|
|
bar = \"\"\"\\\r\n \r\n \r\n a\"\"\"
|
|
|
|
");
|
|
|
|
let table = Table(p.parse().unwrap());
|
|
|
|
assert_eq!(table.lookup("foo").and_then(|k| k.as_str()), Some(""));
|
|
|
|
assert_eq!(table.lookup("bar").and_then(|k| k.as_str()), Some("a"));
|
|
|
|
}
|
2014-06-23 10:58:44 -05:00
|
|
|
}
|