1 #![allow(clippy::unneeded_field_pattern)] 2 #![allow(clippy::toplevel_ref_arg)] 3 4 #[macro_use] 5 mod macros; 6 mod array; 7 mod datetime; 8 mod document; 9 mod errors; 10 mod inline_table; 11 mod key; 12 mod numbers; 13 pub(crate) mod strings; 14 mod table; 15 mod trivia; 16 mod value; 17 18 pub use self::errors::TomlError; 19 pub(crate) use self::key::key as key_parser; 20 pub(crate) use self::value::value as value_parser; 21 22 use crate::document::Document; 23 use crate::key::Key; 24 25 pub struct TomlParser { 26 document: Box<Document>, 27 current_table_path: Vec<Key>, 28 current_table_position: usize, 29 } 30 31 impl Default for TomlParser { default() -> Self32 fn default() -> Self { 33 Self { 34 document: Box::new(Document::new()), 35 current_table_path: Vec::new(), 36 current_table_position: 0, 37 } 38 } 39 } 40 41 #[cfg(test)] 42 mod tests { 43 use crate::parser::*; 44 use combine::stream::state::State; 45 use combine::*; 46 use pretty_assertions::assert_eq; 47 use std; 48 use std::fmt; 49 // Copied from https://github.com/colin-kiegel/rust-pretty-assertions/issues/24 50 /// Wrapper around string slice that makes debug output `{:?}` to print string same way as `{}`. 51 /// Used in different `assert*!` macros in combination with `pretty_assertions` crate to make 52 /// test failures to show nice diffs. 53 #[derive(PartialEq, Eq)] 54 struct PrettyString<'a>(pub &'a str); 55 /// Make diff to display string as multi-line string 56 impl<'a> fmt::Debug for PrettyString<'a> { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result57 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 58 f.write_str(self.0) 59 } 60 } 61 62 macro_rules! parsed_eq { 63 ($parsed:ident, $expected:expr) => {{ 64 assert!($parsed.is_ok()); 65 let (v, rest) = $parsed.unwrap(); 66 assert_eq!(v, $expected); 67 assert!(rest.input.is_empty()); 68 }}; 69 } 70 71 macro_rules! parsed_float_eq { 72 ($input:ident, $expected:expr) => {{ 73 let parsed = numbers::float().easy_parse(State::new($input)); 74 assert!(parsed.is_ok()); 75 let (v, rest) = parsed.unwrap(); 76 assert!(($expected - v).abs() < std::f64::EPSILON); 77 assert!(rest.input.is_empty()); 78 }}; 79 } 80 81 macro_rules! parsed_value_eq { 82 ($input:expr) => { 83 let parsed = value::value().easy_parse(State::new(*$input)); 84 assert!(parsed.is_ok()); 85 let (v, rest) = parsed.unwrap(); 86 assert_eq!(v.to_string(), *$input); 87 assert!(rest.input.is_empty()); 88 }; 89 } 90 91 macro_rules! parsed_date_time_eq { 92 ($input:expr, $is:ident) => {{ 93 let parsed = value::value().easy_parse(State::new(*$input)); 94 assert!(parsed.is_ok()); 95 let (v, rest) = parsed.unwrap(); 96 assert_eq!(v.to_string(), *$input); 97 assert!(rest.input.is_empty()); 98 assert!(v.is_date_time()); 99 assert!(v.as_date_time().unwrap().$is()); 100 }}; 101 } 102 103 #[test] integers()104 fn integers() { 105 let cases = [ 106 ("+99", 99), 107 ("42", 42), 108 ("0", 0), 109 ("-17", -17), 110 ("1_000", 1_000), 111 ("5_349_221", 5_349_221), 112 ("1_2_3_4_5", 1_2_3_4_5), 113 (&std::i64::MIN.to_string()[..], std::i64::MIN), 114 (&std::i64::MAX.to_string()[..], std::i64::MAX), 115 ]; 116 for &(input, expected) in &cases { 117 let parsed = numbers::integer().easy_parse(State::new(input)); 118 parsed_eq!(parsed, expected); 119 } 120 121 let overflow = "1000000000000000000000000000000000"; 122 let parsed = numbers::integer().easy_parse(State::new(overflow)); 123 assert!(parsed.is_err()); 124 } 125 126 #[test] floats()127 fn floats() { 128 let cases = [ 129 ("+1.0", 1.0), 130 ("3.1419", 3.1419), 131 ("-0.01", -0.01), 132 ("5e+22", 5e+22), 133 ("1e6", 1e6), 134 ("-2E-2", -2E-2), 135 ("6.626e-34", 6.626e-34), 136 ("9_224_617.445_991_228_313", 9_224_617.445_991_228_313), 137 ("-1.7976931348623157e+308", std::f64::MIN), 138 ("1.7976931348623157e+308", std::f64::MAX), 139 // ("1e+400", std::f64::INFINITY), 140 ]; 141 for &(input, expected) in &cases { 142 parsed_float_eq!(input, expected); 143 } 144 } 145 146 #[test] basic_string()147 fn basic_string() { 148 let input = 149 r#""I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF. \U0002070E""#; 150 let parsed = strings::string().easy_parse(State::new(input)); 151 parsed_eq!( 152 parsed, 153 "I\'m a string. \"You can quote me\". Name\tJosé\nLocation\tSF. \u{2070E}" 154 ) 155 } 156 157 #[test] ml_basic_string()158 fn ml_basic_string() { 159 let cases = [ 160 ( 161 r#"""" 162 Roses are red 163 Violets are blue""""#, 164 r#"Roses are red 165 Violets are blue"#, 166 ), 167 (r#"""" \""" """"#, " \"\"\" "), 168 (r#"""" \\""""#, " \\"), 169 ]; 170 171 for &(input, expected) in &cases { 172 let parsed = strings::string().easy_parse(State::new(input)); 173 parsed_eq!(parsed, expected); 174 } 175 176 let invalid_cases = [r#"""" """#, r#"""" \""""#]; 177 178 for input in &invalid_cases { 179 let parsed = strings::ml_basic_string().easy_parse(State::new(*input)); 180 assert!(parsed.is_err()); 181 } 182 } 183 184 #[test] ml_basic_string_escape_ws()185 fn ml_basic_string_escape_ws() { 186 let inputs = [ 187 r#"""" 188 The quick brown \ 189 190 191 fox jumps over \ 192 the lazy dog.""""#, 193 r#""""\ 194 The quick brown \ 195 fox jumps over \ 196 the lazy dog.\ 197 """"#, 198 ]; 199 for input in &inputs { 200 let parsed = strings::string().easy_parse(State::new(*input)); 201 parsed_eq!(parsed, "The quick brown fox jumps over the lazy dog."); 202 } 203 let empties = [ 204 r#""""\ 205 """"#, 206 r#"""" 207 \ 208 \ 209 """"#, 210 ]; 211 for empty in &empties { 212 let parsed = strings::string().easy_parse(State::new(*empty)); 213 parsed_eq!(parsed, ""); 214 } 215 } 216 217 #[test] literal_string()218 fn literal_string() { 219 let inputs = [ 220 r#"'C:\Users\nodejs\templates'"#, 221 r#"'\\ServerX\admin$\system32\'"#, 222 r#"'Tom "Dubs" Preston-Werner'"#, 223 r#"'<\i\c*\s*>'"#, 224 ]; 225 226 for input in &inputs { 227 let parsed = strings::string().easy_parse(State::new(*input)); 228 parsed_eq!(parsed, &input[1..input.len() - 1]); 229 } 230 } 231 232 #[test] ml_literal_string()233 fn ml_literal_string() { 234 let input = r#"'''I [dw]on't need \d{2} apples'''"#; 235 let parsed = strings::string().easy_parse(State::new(input)); 236 parsed_eq!(parsed, &input[3..input.len() - 3]); 237 let input = r#"''' 238 The first newline is 239 trimmed in raw strings. 240 All other whitespace 241 is preserved. 242 '''"#; 243 let parsed = strings::string().easy_parse(State::new(input)); 244 parsed_eq!(parsed, &input[4..input.len() - 3]); 245 } 246 247 #[test] offset_date_time()248 fn offset_date_time() { 249 let inputs = [ 250 "1979-05-27T07:32:00Z", 251 "1979-05-27T00:32:00-07:00", 252 "1979-05-27T00:32:00.999999-07:00", 253 ]; 254 for input in &inputs { 255 parsed_date_time_eq!(input, is_offset_date_time); 256 } 257 } 258 259 #[test] local_date_time()260 fn local_date_time() { 261 let inputs = ["1979-05-27T07:32:00", "1979-05-27T00:32:00.999999"]; 262 for input in &inputs { 263 parsed_date_time_eq!(input, is_local_date_time); 264 } 265 } 266 267 #[test] local_date()268 fn local_date() { 269 let inputs = ["1979-05-27", "2017-07-20"]; 270 for input in &inputs { 271 parsed_date_time_eq!(input, is_local_date); 272 } 273 } 274 275 #[test] local_time()276 fn local_time() { 277 let inputs = ["07:32:00", "00:32:00.999999"]; 278 for input in &inputs { 279 parsed_date_time_eq!(input, is_local_time); 280 } 281 } 282 283 #[test] trivia()284 fn trivia() { 285 let inputs = [ 286 "", 287 r#" "#, 288 r#" 289 "#, 290 r#" 291 # comment 292 293 # comment2 294 295 296 "#, 297 r#" 298 "#, 299 r#"# comment 300 # comment2 301 302 303 "#, 304 ]; 305 for input in &inputs { 306 let parsed = trivia::ws_comment_newline().easy_parse(State::new(*input)); 307 assert!(parsed.is_ok()); 308 let (t, rest) = parsed.unwrap(); 309 assert!(rest.input.is_empty()); 310 assert_eq!(&t, input); 311 } 312 } 313 314 #[test] arrays()315 fn arrays() { 316 let inputs = [ 317 r#"[]"#, 318 r#"[ ]"#, 319 r#"[ 320 1, 2, 3 321 ]"#, 322 r#"[ 323 1, 324 2, # this is ok 325 ]"#, 326 r#"[# comment 327 # comment2 328 329 330 ]"#, 331 r#"[# comment 332 # comment2 333 1 334 335 #sd 336 , 337 # comment3 338 339 ]"#, 340 r#"[1]"#, 341 r#"[1,]"#, 342 r#"[ "all", 'strings', """are the same""", '''type''']"#, 343 r#"[ 100, -2,]"#, 344 r#"[1, 2, 3]"#, 345 r#"[1.1, 2.1, 3.1]"#, 346 r#"["a", "b", "c"]"#, 347 r#"[ [ 1, 2 ], [3, 4, 5] ]"#, 348 r#"[ [ 1, 2 ], ["a", "b", "c"] ]"#, 349 r#"[ { x = 1, a = "2" }, {a = "a",b = "b", c = "c"} ]"#, 350 ]; 351 for input in &inputs { 352 parsed_value_eq!(input); 353 } 354 355 let invalid_inputs = [r#"["#, r#"[,]"#, r#"[,2]"#, r#"[1e165,,]"#, r#"[ 1, 2.0 ]"#]; 356 for input in &invalid_inputs { 357 let parsed = array::array().easy_parse(State::new(*input)); 358 assert!(parsed.is_err()); 359 } 360 } 361 362 #[test] inline_tables()363 fn inline_tables() { 364 let inputs = [ 365 r#"{}"#, 366 r#"{ }"#, 367 r#"{a = 1e165}"#, 368 r#"{ hello = "world", a = 1}"#, 369 ]; 370 for input in &inputs { 371 parsed_value_eq!(input); 372 } 373 let invalid_inputs = [r#"{a = 1e165"#, r#"{ hello = "world", a = 2, hello = 1}"#]; 374 for input in &invalid_inputs { 375 let parsed = inline_table::inline_table().easy_parse(State::new(*input)); 376 assert!(parsed.is_err()); 377 } 378 } 379 380 #[test] keys()381 fn keys() { 382 let cases = [ 383 ("a", "a"), 384 (r#""hello\n ""#, "hello\n "), 385 (r#"'hello\n '"#, "hello\\n "), 386 ]; 387 388 for &(input, expected) in &cases { 389 let parsed = key::key().easy_parse(State::new(input)); 390 assert!(parsed.is_ok()); 391 let ((.., k), rest) = parsed.unwrap(); 392 assert_eq!(k, expected); 393 assert_eq!(rest.input.len(), 0); 394 } 395 } 396 397 #[test] values()398 fn values() { 399 let inputs = [ 400 "1979-05-27T00:32:00.999999", 401 "-239", 402 "1e200", 403 "9_224_617.445_991_228_313", 404 r#"'''I [dw]on't need \d{2} apples'''"#, 405 r#"''' 406 The first newline is 407 trimmed in raw strings. 408 All other whitespace 409 is preserved. 410 '''"#, 411 r#""Jos\u00E9\n""#, 412 r#""\\\"\b\/\f\n\r\t\u00E9\U000A0000""#, 413 r#"{ hello = "world", a = 1}"#, 414 r#"[ { x = 1, a = "2" }, {a = "a",b = "b", c = "c"} ]"#, 415 ]; 416 for input in &inputs { 417 parsed_value_eq!(input); 418 } 419 } 420 421 #[test] documents()422 fn documents() { 423 let documents = [ 424 r#" 425 # This is a TOML document. 426 427 title = "TOML Example" 428 429 [owner] 430 name = "Tom Preston-Werner" 431 dob = 1979-05-27T07:32:00-08:00 # First class dates 432 433 [database] 434 server = "192.168.1.1" 435 ports = [ 8001, 8001, 8002 ] 436 connection_max = 5000 437 enabled = true 438 439 [servers] 440 441 # Indentation (tabs and/or spaces) is allowed but not required 442 [servers.alpha] 443 ip = "10.0.0.1" 444 dc = "eqdc10" 445 446 [servers.beta] 447 ip = "10.0.0.2" 448 dc = "eqdc10" 449 450 [clients] 451 data = [ ["gamma", "delta"], [1, 2] ] 452 453 # Line breaks are OK when inside arrays 454 hosts = [ 455 "alpha", 456 "omega" 457 ] 458 459 'some.wierd .stuff' = """ 460 like 461 that 462 # """ # this broke my sintax highlighting 463 " also. like " = ''' 464 that 465 ''' 466 double = 2e39 # this number looks familiar 467 # trailing comment"#, 468 r#""#, 469 r#" "#, 470 r#" hello = 'darkness' # my old friend 471 "#, 472 ]; 473 for document in &documents { 474 let doc = TomlParser::parse(document); 475 476 assert!(doc.is_ok()); 477 let doc = doc.unwrap(); 478 479 assert_eq!(PrettyString(document), PrettyString(&doc.to_string())); 480 } 481 482 let invalid_inputs = [r#" hello = 'darkness' # my old friend 483 $"#]; 484 for document in &invalid_inputs { 485 let doc = TomlParser::parse(document); 486 487 assert!(doc.is_err()); 488 } 489 } 490 } 491