char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
f4a0f5b
main
1use serde_fjson:: syntax::{ self as config, Member , Value , ValueKind }; 2 3fn members ( value : & Value ) ->& [ Member ] { 4let ValueKind :: Object ( members) =& value. kind else { 5panic! ( "Expected an object: {value:?}" ); 6}; 7 members 8} 9 10fn field < ' a >( value : &' a Value , key : & str ) ->&' a Value { 11& members ( value) 12. iter () 13. find ( |member| member. key == key) 14. unwrap () 15. value 16} 17 18# [ test ] 19fn parses_a_package_config_with_or_without_root_braces () { 20let source =r#" 21// Dependencies are private to this package. 22imports: { 23assert: 'jsr:@std/assert@^1', 24"@my/library": "../library/mod.ts", 25}, 26options: { 27enabled: true, 28fallback: null, 29targets: ["linux", /* another platform */ "macos",], 30}, 31"# ; 32for sourcein [ source. to_owned (), format! ( "{{{source}}}" )] { 33let value = config:: parse_object ( & source). unwrap (); 34let imports =field ( & value, "imports" ); 35assert_eq! ( 36field ( imports, "assert" ). kind , 37ValueKind :: String ( "jsr:@std/assert@^1" . into ()) 38); 39assert_eq! ( 40field ( imports, "@my/library" ). kind , 41ValueKind :: String ( "../library/mod.ts" . into ()) 42); 43let options =field ( & value, "options" ); 44assert_eq! ( field ( options, "enabled" ). kind , ValueKind :: Bool ( true )); 45assert_eq! ( field ( options, "fallback" ). kind , ValueKind :: Null ); 46let ValueKind :: Array ( targets) =& field ( options, "targets" ). kind else { 47panic! () 48}; 49assert_eq! ( 50 targets. iter (). map ( |value|& value. kind ). collect ::< Vec < _ >>(), 51vec! [ 52& ValueKind :: String ( "linux" . into ()), 53& ValueKind :: String ( "macos" . into ()), 54] 55); 56} 57} 58 59# [ test ] 60fn handles_empty_containers_and_comments_at_token_boundaries () { 61for sourcein [ 62"" , 63" \t\r\n" , 64"// only a comment" , 65"/* comment */" , 66"{}" , 67"{ /* comment */ }" , 68] { 69assert! ( 70members ( & config:: parse_object ( source). unwrap ()). is_empty (), 71"{source}" 72); 73} 74let value = config:: parse_object ( "a/*1*/:/*2*/[/*3*/]/*4*/,/*5*/b: {/*6*/},// eof" ). unwrap (); 75assert_eq! ( field ( & value, "a" ). kind , ValueKind :: Array ( vec! [])); 76assert! ( members ( field ( & value, "b" )). is_empty ()); 77assert_eq! ( 78field ( & config:: parse_object ( "a: false // eof" ). unwrap (), "a" ). kind , 79ValueKind :: Bool ( false ) 80); 81} 82 83# [ test ] 84fn implicit_commas_separate_map_entries_and_array_items () { 85for newlinein [ "\n" , "\r\n" , "\r" ] { 86for separatorin [ 87 newline. to_owned (), 88format! ( " // comment{newline}" ), 89format! ( " /* comment */{newline}/* next */ " ), 90format! ( " /* multiline{newline}comment */{newline}" ), 91format! ( "{newline}/* multiline{newline}comment */ " ), 92format! ( "{newline}// blank{newline}{newline}" ), 93format! ( ",{newline}" ), 94format! ( "{newline},{newline}" ), 95] { 96let source =format! ( 97"a: 1{separator}b: [true{separator}false{separator}null{newline}]{newline}" 98); 99for sourcein [ source. clone (), format! ( "{{{newline}{source}}}" )] { 100let value = config:: parse_object ( & source). unwrap (); 101assert_eq! ( members ( & value). len (), 2 , "{source:?}" ); 102assert_eq! ( field ( & value, "a" ). kind , ValueKind :: Number ( "1" . into ())); 103assert_eq! ( & source[ field ( & value, "a" ). span . clone ()], "1" ); 104let ValueKind :: Array ( items) =& field ( & value, "b" ). kind else { 105panic! () 106}; 107assert_eq! ( 108 items. iter (). map ( |item|& item. kind ). collect ::< Vec < _ >>(), 109vec! [ 110& ValueKind :: Bool ( true ), 111& ValueKind :: Bool ( false ), 112& ValueKind :: Null 113] 114); 115for ( item, spelling) in items. iter (). zip ([ "true" , "false" , "null" ]) { 116assert_eq! ( & source[ item. span . clone ()], spelling); 117} 118} 119} 120} 121} 122 123# [ test ] 124fn newlines_only_separate_completed_entries_at_the_current_level () { 125let source ="a\n:\n[\n{\nx\n:\n1\ny: 2\n}\n[\n3\n4\n]\n]\nb:\nfalse" ; 126let value = config:: parse_object ( source). unwrap (); 127assert_eq! ( members ( & value). len (), 2 ); 128assert_eq! ( field ( & value, "b" ). kind , ValueKind :: Bool ( false )); 129let ValueKind :: Array ( items) =& field ( & value, "a" ). kind else { 130panic! () 131}; 132assert_eq! ( items. len (), 2 ); 133assert_eq! ( field ( & items[ 0 ], "x" ). kind , ValueKind :: Number ( "1" . into ())); 134assert_eq! ( field ( & items[ 0 ], "y" ). kind , ValueKind :: Number ( "2" . into ())); 135assert_eq! ( & source[ items[ 0 ]. span . clone ()], "{\nx\n:\n1\ny: 2\n}" ); 136let ValueKind :: Array ( numbers) =& items[ 1 ]. kind else { 137panic! () 138}; 139assert_eq! ( 140 numbers 141. iter () 142. map ( |number|& number. kind ) 143. collect ::< Vec < _ >>(), 144vec! [ 145& ValueKind :: Number ( "3" . into ()), 146& ValueKind :: Number ( "4" . into ()) 147] 148); 149} 150 151# [ test ] 152fn requires_explicit_commas_without_a_separator_newline () { 153for sourcein [ 154"a: 1 b: 2" , 155"{a: true b: false}" , 156"a: [1 2]" , 157"a: ['one' 'two']" , 158"a: 1 /* comment */ b: 2" , 159"a: [1 /* comment */ 2]" , 160"a: 1 /* multiline\ncomment */ b: 2" , 161"a: [1 /* multiline\r\ncomment */ 2]" , 162"a: [\n1\n] b: 2" , 163"a: [{\nx: 1\n} {}]" , 164"a\n:\n1 b: 2" , 165"a: ['one\\ntwo' 'three']" , 166] { 167let error = config:: parse_object ( source). unwrap_err (); 168assert! ( 169 error. message . contains ( "Expected `,` or newline" ), 170"{source}: {error}" 171); 172} 173for sourcein [ 174"a: 1," , 175"{a: 1,}" , 176"a: [1,]," , 177"a: 1,\nb: 2," , 178"a: 1\n," , 179"{a: 1\n,}" , 180"a: [1\n,]" , 181] { 182 config:: parse_object ( source). unwrap (); 183} 184} 185 186# [ test ] 187fn strings_decode_escapes_without_interpreting_comment_markers () { 188let value = config:: parse_object ( 189r#" 190url: "https://example.com/*path*/", 191text: '\"\'\\\/\b\f\n\r\t', 192unicode: "α\u0061\uD83D\uDE00", 193"quoted\u0020key": 'value', 194"# , 195) 196. unwrap (); 197assert_eq! ( 198field ( & value, "url" ). kind , 199ValueKind :: String ( "https://example.com/*path*/" . into ()) 200); 201assert_eq! ( 202field ( & value, "text" ). kind , 203ValueKind :: String ( "\"'\\/\u{0008}\u{000c}\n\r\t" . into ()) 204); 205assert_eq! ( 206field ( & value, "unicode" ). kind , 207ValueKind :: String ( "αa😀" . into ()) 208); 209assert_eq! ( 210field ( & value, "quoted key" ). kind , 211ValueKind :: String ( "value" . into ()) 212); 213} 214 215# [ test ] 216fn accepts_identifier_keys_but_not_bare_string_values () { 217let value = 218 config:: parse_object ( "_key: 1, $key: 2, key123: 3, true: false, '日本語': 'ok'" ). unwrap (); 219assert_eq! ( 220members ( & value) 221. iter () 222. map ( |member| member. key . as_str ()) 223. collect ::< Vec < _ >>(), 224vec! [ "_key" , "$key" , "key123" , "true" , "日本語" ] 225); 226for sourcein [ "a: bare" , "a: undefined" , "a: trueish" , "a: Null" ] { 227assert! ( 228 config:: parse_object ( source) 229. unwrap_err () 230. message 231. contains ( "strings must be quoted" ) 232); 233} 234for sourcein [ "123: true" , "日本語: 1" , "@scope/pkg: 1" , "some-key: 1" ] { 235assert! ( config:: parse_object ( source). is_err (), "{source}" ); 236} 237} 238 239# [ test ] 240fn decimal_numbers_keep_their_precision_and_spelling () { 241for spellingin [ 242"0" , 243"-0" , 244"42" , 245"-12.34" , 246"1e3" , 247"2.5E-10" , 248"6e+2" , 249"9007199254740993" , 250"1e9999" , 251] { 252let source =format! ( "value: {spelling}" ); 253assert_eq! ( 254field ( & config:: parse_object ( & source). unwrap (), "value" ). kind , 255ValueKind :: Number ( spelling. into ()) 256); 257} 258for spellingin [ 259"-" , 260"01" , 261"-01" , 262"1." , 263".5" , 264"+1" , 265"1e" , 266"1e+" , 267"0x10" , 268"1_000" , 269"1n" , 270"NaN" , 271"Infinity" , 272"-Infinity" , 273"1.2.3" , 274] { 275assert! ( 276 config:: parse_object ( & format! ( "value: {spelling}" )). is_err (), 277"{spelling}" 278); 279} 280} 281 282# [ test ] 283fn rejects_duplicate_decoded_keys_and_identifies_both_definitions () { 284let source ="imports: {\n assert: 'first'\n '\\u0061ssert': 'second'\n}" ; 285let error = config:: parse_object ( source). unwrap_err (); 286assert! ( 287 error. message . contains ( "Duplicate key \"assert\"" ), 288"{error}" 289); 290assert_eq! ( & source[ error. span . clone ()], "'\\u0061ssert'" ); 291assert_eq! ( & source[ error. previous . clone (). unwrap ()], "assert" ); 292assert_eq! ( 293 error. render ( "avril.conf" , source), 294"avril.conf:3:3: Duplicate key \"assert\"\navril.conf:2:3: First defined here" 295); 296 config:: parse_object ( "a: {key: 1}, b: {key: 2}" ). unwrap (); 297} 298 299# [ test ] 300fn retains_original_source_spans_for_keys_and_values () { 301let source ="/* lead */ imports: { '日本語': ['😀', -12.5], }, // tail" ; 302let value = config:: parse_object ( source). unwrap (); 303let imports =field ( & value, "imports" ); 304assert_eq! ( 305& source[ imports. span . clone ()], 306"{ '日本語': ['😀', -12.5], }" 307); 308let entry =& members ( imports)[ 0 ]; 309assert_eq! ( & source[ entry. key_span . clone ()], "'日本語'" ); 310assert_eq! ( & source[ entry. value . span . clone ()], "['😀', -12.5]" ); 311let ValueKind :: Array ( elements) =& entry. value . kind else { 312panic! () 313}; 314assert_eq! ( & source[ elements[ 0 ]. span . clone ()], "'😀'" ); 315assert_eq! ( & source[ elements[ 1 ]. span . clone ()], "-12.5" ); 316} 317 318# [ test ] 319fn diagnostics_use_original_lines_and_unicode_character_columns () { 320let source ="label: '😀',\n imports: {\n x: 'α', y 'bad',\n },\n" ; 321let error = config:: parse_object ( source). unwrap_err (); 322assert_eq! ( 323 error. render ( "avril.conf" , source), 324"avril.conf:3:15: Expected `:`" 325); 326assert_eq! ( & source[ error. span ], "'" ); 327for newlinein [ "\r" , "\r\n" , "\n" ] { 328let source =format! ( "a: true,{newline}// heading{newline}b:" ); 329let error = config:: parse_object ( & source). unwrap_err (); 330assert_eq! ( 331 error. render ( "avril.conf" , & source), 332"avril.conf:3:3: Expected a value" 333); 334} 335let source ="a: 1,\nb:" ; 336let error = config:: parse_object ( source). unwrap_err (); 337assert_eq! ( error. span , source. len () ..source. len ()); 338assert_eq! ( 339 error. render ( "avril.conf" , source), 340"avril.conf:2:3: Expected a value" 341); 342} 343 344# [ test ] 345fn rejects_malformed_or_executable_input () { 346for ( source, message) in [ 347( "[]" , "object key" ), 348( "42" , "object key" ), 349( "{a: 1} b: 2" , "after the root object" ), 350( "{a: 1}," , "after the root object" ), 351( "a: {" , "Expected `}`" ), 352( "a: [" , "Expected `]`" ), 353( "a: [1" , "Expected `]`" ), 354( "a: {b: 1" , "Expected `}`" ), 355( "a: 1,," , "object key" ), 356( "a: [,]" , "Expected a value" ), 357( "a: [1,,]" , "Expected a value" ), 358( "a: 1;" , "Expected `,` or newline" ), 359( "a = 1" , "Expected `:`" ), 360( "a: () => 1" , "Expected a value" ), 361( "a: `template`" , "Expected a value" ), 362( "a: 'unterminated" , "Unterminated string" ), 363( "a: 'new\nline'" , "Control characters" ), 364( "a: '\0'" , "Control characters" ), 365( r#"a: '\q'"# , "Invalid string escape" ), 366( r#"a: '\u12x4'"# , "hexadecimal digits" ), 367( r#"a: '\uDC00'"# , "Unexpected low Unicode surrogate" ), 368( r#"a: '\uD800\u0041'"# , "Expected a low Unicode surrogate" ), 369( r#"a: '\uD800'"# , "Unpaired Unicode surrogate" ), 370( "/* never closed" , "Unterminated block comment" ), 371( "a: 1 / nope" , "Expected `,` or newline" ), 372] { 373let error = config:: parse_object ( source). unwrap_err (); 374assert! ( error. message . contains ( message), "{source:?}: {error}" ); 375assert! ( source. get ( error. span . clone ()). is_some (), "{error:?}" ); 376} 377} 378 379# [ test ] 380fn bounds_nesting_without_rejecting_large_flat_configs () { 381let nested =format! ( "value: {}0{}" , "[" . repeat ( 129 ), "]" . repeat ( 129 )); 382assert! ( 383 config:: parse_object ( & nested) 384. unwrap_err () 385. message 386. contains ( "nesting" ) 387); 388let nested =format! ( "value: {}0{}" , "[" . repeat ( 128 ), "]" . repeat ( 128 )); 389 config:: parse_object ( & nested). unwrap (); 390let objects =format! ( "value: {}0{}" , "{a:" . repeat ( 129 ), "}" . repeat ( 129 )); 391assert! ( 392 config:: parse_object ( & objects) 393. unwrap_err () 394. message 395. contains ( "nesting" ) 396); 397let flat =format! ( "value: [{}]" , "0," . repeat ( 10_000 )); 398let value = config:: parse_object ( & flat). unwrap (); 399let ValueKind :: Array ( elements) =& field ( & value, "value" ). kind else { 400panic! () 401}; 402assert_eq! ( elements. len (), 10_000 ); 403} 404 405# [ test ] 406fn truncated_input_always_produces_valid_diagnostic_spans () { 407let source = 408r#"/* lead */ imports: {'日本語': "😀\uD83D\uDE00",}, flags: [true, false, null, -1e+2],"# ; 409for offsetin source 410. char_indices () 411. map ( |( offset, _) | offset) 412. chain ([ source. len ()]) 413{ 414let prefix =& source[ ..offset]; 415if let Err ( error) = config:: parse_object ( prefix) { 416assert! ( 417 prefix. get ( error. span . clone ()). is_some (), 418"{prefix:?}: {error:?}" 419); 420 error. render ( "avril.conf" , prefix); 421} 422} 423}