From 9c508237bd0f6ebde2693e98334114d536065b2d Mon Sep 17 00:00:00 2001 From: Larpon Date: Sat, 13 Nov 2021 10:17:35 +0100 Subject: [PATCH] toml: support for `[a."b.c"]` quoted keys (#12444) --- vlib/toml/any.v | 38 ++--- vlib/toml/ast/types.v | 45 ------ vlib/toml/parser/parser.v | 160 ++++++++++++-------- vlib/toml/tests/burntsushi.toml-test_test.v | 9 +- vlib/toml/tests/quoted_keys_test.v | 12 ++ vlib/toml/toml.v | 50 +++--- vlib/toml/util/util.v | 41 +++++ 7 files changed, 198 insertions(+), 157 deletions(-) create mode 100644 vlib/toml/tests/quoted_keys_test.v diff --git a/vlib/toml/any.v b/vlib/toml/any.v index 93307d8db3..f9b5756fdb 100644 --- a/vlib/toml/any.v +++ b/vlib/toml/any.v @@ -4,6 +4,7 @@ module toml import time +import toml.util // Pretty much all the same builtin types as the `json2.Any` type plus `time.Time` pub type Any = Null @@ -150,26 +151,27 @@ pub fn (a Any) datetime() time.Time { } } +// value queries a value from the map. +// `key` should be in "dotted" form (`a.b.c`). +// `key` supports quoted keys like `a."b.c"`. pub fn (m map[string]Any) value(key string) ?Any { - // return m[key] ? - key_split := key.split('.') - // util.printdbg(@MOD + '.' + @STRUCT + '.' + @FN, ' getting "${key_split[0]}"') - if key_split[0] in m.keys() { - value := m[key_split[0]] or { - return error(@MOD + '.' + @STRUCT + '.' + @FN + ' key "$key" does not exist') - } - // `match` isn't currently very suitable for these types of sum type constructs... - if value is map[string]Any { - nm := (value as map[string]Any) - next_key := key_split[1..].join('.') - if next_key == '' { - return value - } - return nm.value(next_key) - } - return value + key_split := util.parse_dotted_key(key) ? + return m.value_(key_split) +} + +fn (m map[string]Any) value_(key []string) ?Any { + value := m[key[0]] or { + return error(@MOD + '.' + @STRUCT + '.' + @FN + ' key "${key[0]}" does not exist') } - return error(@MOD + '.' + @STRUCT + '.' + @FN + ' key "$key" does not exist') + // `match` isn't currently very suitable for these types of sum type constructs... + if value is map[string]Any { + if key.len <= 1 { + return value + } + nm := (value as map[string]Any) + return nm.value_(key[1..]) + } + return value } pub fn (a []Any) as_strings() []string { diff --git a/vlib/toml/ast/types.v b/vlib/toml/ast/types.v index 588c6449fa..8e3ac873e0 100644 --- a/vlib/toml/ast/types.v +++ b/vlib/toml/ast/types.v @@ -63,51 +63,6 @@ pub fn (dtt DateTimeType) str() string { return dtt.text } -// value queries a value from the map. -// `key` should be in "dotted" form (`a.b.c`). -pub fn (v map[string]Value) value(key string) &Value { - null := &Value(Null{}) - key_split := key.split('.') - // util.printdbg(@MOD + '.' + @STRUCT + '.' + @FN, ' retreiving value at "$key"') - if key_split[0] in v.keys() { - value := v[key_split[0]] or { - return null - // TODO return error(@MOD + '.' + @STRUCT + '.' + @FN + ' key "$key" does not exist') - } - // `match` isn't currently very suitable for these types of sum type constructs... - if value is map[string]Value { - m := (value as map[string]Value) - next_key := key_split[1..].join('.') - if next_key == '' { - return &value - } - return m.value(next_key) - } - return &value - } - return null - // TODO return error(@MOD + '.' + @STRUCT + '.' + @FN + ' key "$key" does not exist') -} - -// exists returns true if the "dotted" `key` path exists in the map. -pub fn (v map[string]Value) exists(key string) bool { - key_split := key.split('.') - if key_split[0] in v.keys() { - value := v[key_split[0]] or { return false } - // `match` isn't currently very suitable for these types of sum type constructs... - if value is map[string]Value { - m := (value as map[string]Value) - next_key := key_split[1..].join('.') - if next_key == '' { - return true - } - return m.exists(next_key) - } - return true - } - return false -} - // Comment is the data representation of a TOML comment (`# This is a comment`). pub struct Comment { pub: diff --git a/vlib/toml/parser/parser.v b/vlib/toml/parser/parser.v index fdb02afe22..1aea840085 100644 --- a/vlib/toml/parser/parser.v +++ b/vlib/toml/parser/parser.v @@ -14,6 +14,21 @@ pub const ( space_formatting = [token.Kind.whitespace, .tab] ) +type DottedKey = []string + +pub fn (dk DottedKey) str() string { + return dk.join('.') +} + +pub fn (a []DottedKey) has(target DottedKey) bool { + for dk in a { + if dk == target { + return true + } + } + return false +} + // Parser contains the necessary fields for keeping the state of the parsing process. pub struct Parser { pub: @@ -26,10 +41,11 @@ mut: tokens []token.Token // To be able to peek more than one token ahead. skip_next bool // The root map (map is called table in TOML world) - root_map map[string]ast.Value - root_map_key string + root_map map[string]ast.Value + root_map_key DottedKey + explicit_declared []DottedKey // Array of Tables state - last_aot string + last_aot DottedKey last_aot_index int // Root of the tree ast_root &ast.Root = &ast.Root{} @@ -220,7 +236,7 @@ pub fn (mut p Parser) find_table() ?&map[string]ast.Value { unsafe { t = &p.root_map } - if p.root_map_key == '' { + if p.root_map_key.len == 0 { return t } @@ -229,11 +245,10 @@ pub fn (mut p Parser) find_table() ?&map[string]ast.Value { // sub_table_key returns the logic parts of a dotted key (`a.b.c`) for // use with the `find_sub_table` method. -pub fn (mut p Parser) sub_table_key(key string) (string, string) { - mut ks := key.split('.') - last := ks.last() - ks.delete_last() - return ks.join('.'), last +pub fn (mut p Parser) sub_table_key(key DottedKey) (DottedKey, DottedKey) { + last := [key.last()] + first := key[..key.len - 1] + return first, last } // find_sub_table returns a reference to a map if found in the *root* table given a "dotted" key (`a.b.c`). @@ -241,9 +256,11 @@ pub fn (mut p Parser) sub_table_key(key string) (string, string) { // allocate a new map for the segment. This behavior is needed because you can // reference maps by multiple keys "dotted" (separated by "." periods) in TOML documents. // See also `find_in_table`. -pub fn (mut p Parser) find_sub_table(key string) ?&map[string]ast.Value { - mut ky := p.root_map_key + '.' + key - if p.root_map_key == '' { +pub fn (mut p Parser) find_sub_table(key DottedKey) ?&map[string]ast.Value { + mut ky := DottedKey([]string{}) + ky << p.root_map_key + ky << key + if p.root_map_key.len == 0 { ky = key } util.printdbg(@MOD + '.' + @STRUCT + '.' + @FN, 'locating "$ky" in map ${ptr_str(p.root_map)}') @@ -251,7 +268,7 @@ pub fn (mut p Parser) find_sub_table(key string) ?&map[string]ast.Value { unsafe { t = &p.root_map } - if ky == '' { + if ky.len == 0 { return t } @@ -262,7 +279,7 @@ pub fn (mut p Parser) find_sub_table(key string) ?&map[string]ast.Value { // If some segments of the key does not exist in the input map find_in_table will // allocate a new map for the segment. This behavior is needed because you can // reference maps by multiple keys "dotted" (separated by "." periods) in TOML documents. -pub fn (mut p Parser) find_in_table(mut table map[string]ast.Value, key string) ?&map[string]ast.Value { +pub fn (mut p Parser) find_in_table(mut table map[string]ast.Value, key DottedKey) ?&map[string]ast.Value { // NOTE This code is the result of much trial and error. // I'm still not quite sure *exactly* why it works. All I can leave here is a hope // that this kind of minefield someday will be easier in V :) @@ -271,9 +288,8 @@ pub fn (mut p Parser) find_in_table(mut table map[string]ast.Value, key string) unsafe { t = &table } - ks := key.split('.') unsafe { - for k in ks { + for k in key { if val := t[k] { util.printdbg(@MOD + '.' + @STRUCT + '.' + @FN, 'found key "$k" in $t.keys()') if val is map[string]ast.Value { @@ -296,22 +312,23 @@ pub fn (mut p Parser) find_in_table(mut table map[string]ast.Value, key string) // dotted_key returns a string of the next tokens parsed as // sub/nested/path keys (e.g. `a.b.c`). In TOML, this form of key is referred to as a "dotted" key. -pub fn (mut p Parser) dotted_key() ?string { - util.printdbg(@MOD + '.' + @STRUCT + '.' + @FN, 'parsing nested key...') +pub fn (mut p Parser) dotted_key() ?DottedKey { + util.printdbg(@MOD + '.' + @STRUCT + '.' + @FN, 'parsing dotted key...') + mut dotted_key := DottedKey([]string{}) key := p.key() ? p.ignore_while_peek(parser.space_formatting) - mut text := key.str() + dotted_key << key.str() for p.peek_tok.kind == .period { p.next() ? // . p.check(.period) ? p.ignore_while(parser.space_formatting) next_key := p.key() ? - text += '.' + next_key.text + dotted_key << next_key.text p.ignore_while_peek(parser.space_formatting) } p.next() ? - util.printdbg(@MOD + '.' + @STRUCT + '.' + @FN, 'parsed nested key `$text` now at "$p.tok.kind" "$p.tok.lit"') - return text + util.printdbg(@MOD + '.' + @STRUCT + '.' + @FN, 'parsed dotted key `$dotted_key` now at "$p.tok.kind" "$p.tok.lit"') + return dotted_key } // root_table parses next tokens into the root map of `ast.Value`s. @@ -356,7 +373,7 @@ pub fn (mut p Parser) root_table() ? { t := p.find_sub_table(sub_table) ? unsafe { util.printdbg(@MOD + '.' + @STRUCT + '.' + @FN, 'setting "$key" = $val.to_json() in table ${ptr_str(t)}') - t[key] = val + t[key.str()] = val } } else { p.ignore_while(parser.space_formatting) @@ -403,6 +420,14 @@ pub fn (mut p Parser) root_table() ? { // Parse `[d.e.f]` p.ignore_while(parser.space_formatting) p.root_map_key = p.dotted_key() ? + + // Disallow redeclaring the key + if p.explicit_declared.has(p.root_map_key) { + return error(@MOD + '.' + @STRUCT + '.' + @FN + + ' key `$p.root_map_key` is already explicitly declared. Unexpected redeclaration at "$p.tok.kind" "$p.tok.lit" in this (excerpt): "...${p.excerpt()}..."') + } + p.explicit_declared << p.root_map_key + p.ignore_while(parser.space_formatting) util.printdbg(@MOD + '.' + @STRUCT + '.' + @FN, 'setting root map key to `$p.root_map_key` at "$p.tok.kind" "$p.tok.lit"') p.expect(.rsbr) ? @@ -410,7 +435,15 @@ pub fn (mut p Parser) root_table() ? { } else { // Parse `[key]` key := p.key() ? - p.root_map_key = key.str() + p.root_map_key = DottedKey([key.str()]) + + // Disallow redeclaring the key + if p.explicit_declared.has(p.root_map_key) { + return error(@MOD + '.' + @STRUCT + '.' + @FN + + ' key `$p.root_map_key` is already explicitly declared. Unexpected redeclaration at "$p.tok.kind" "$p.tok.lit" in this (excerpt): "...${p.excerpt()}..."') + } + p.explicit_declared << p.root_map_key + util.printdbg(@MOD + '.' + @STRUCT + '.' + @FN, 'setting root map key to `$p.root_map_key` at "$p.tok.kind" "$p.tok.lit"') p.next() ? p.expect(.rsbr) ? @@ -493,7 +526,7 @@ pub fn (mut p Parser) inline_table(mut tbl map[string]ast.Value) ? { mut t := p.find_in_table(mut tbl, sub_table) ? unsafe { util.printdbg(@MOD + '.' + @STRUCT + '.' + @FN, 'inserting @6 "$key" = $val.to_json() into ${ptr_str(t)}') - t[key] = val + t[key.str()] = val } } else { p.ignore_while(parser.space_formatting) @@ -539,25 +572,26 @@ pub fn (mut p Parser) array_of_tables(mut table map[string]ast.Value) ? { p.ignore_while(parser.all_formatting) - key_str := key.str() + dotted_key := DottedKey([key.str()]) + dotted_key_str := dotted_key.str() unsafe { - if val := table[key_str] { + if val := table[dotted_key_str] { if val is []ast.Value { - arr := &(table[key_str] as []ast.Value) + arr := &(table[dotted_key_str] as []ast.Value) arr << p.array_of_tables_contents() ? - table[key_str] = arr + table[dotted_key_str] = arr } else { return error(@MOD + '.' + @STRUCT + '.' + @FN + - ' table[$key_str] is not an array. (excerpt): "...${p.excerpt()}..."') + ' table[$dotted_key_str] is not an array. (excerpt): "...${p.excerpt()}..."') } } else { - table[key_str] = p.array_of_tables_contents() ? + table[dotted_key_str] = p.array_of_tables_contents() ? } } - p.last_aot = key_str + p.last_aot = dotted_key unsafe { - arr := &(table[p.last_aot] as []ast.Value) + arr := &(table[p.last_aot.str()] as []ast.Value) p.last_aot_index = arr.len - 1 } } @@ -585,7 +619,7 @@ pub fn (mut p Parser) array_of_tables_contents() ?[]ast.Value { mut t := p.find_in_table(mut tbl, sub_table) ? unsafe { util.printdbg(@MOD + '.' + @STRUCT + '.' + @FN, 'inserting @6 "$key" = $val.to_json() into ${ptr_str(t)}') - t[key] = val + t[key.str()] = val } } else { key, val := p.key_value() ? @@ -607,15 +641,17 @@ pub fn (mut p Parser) array_of_tables_contents() ?[]ast.Value { pub fn (mut p Parser) double_array_of_tables(mut table map[string]ast.Value) ? { util.printdbg(@MOD + '.' + @STRUCT + '.' + @FN, 'parsing array of tables of arrays "$p.tok.kind" "$p.tok.lit"') + mut dotted_key := DottedKey([]string{}) + key := p.key() ? - mut key_str := key.str() + dotted_key << key.str() for p.peek_tok.kind == .period { p.next() ? // . p.check(.period) ? next_key := p.key() ? - key_str += '.' + next_key.text + dotted_key << next_key.text } - util.printdbg(@MOD + '.' + @STRUCT + '.' + @FN, 'parsed nested key `$key_str` now at "$p.tok.kind" "$p.tok.lit"') + util.printdbg(@MOD + '.' + @STRUCT + '.' + @FN, 'parsed dotted key `$dotted_key` now at "$p.tok.kind" "$p.tok.lit"') p.next() ? p.check(.rsbr) ? @@ -623,15 +659,13 @@ pub fn (mut p Parser) double_array_of_tables(mut table map[string]ast.Value) ? { p.ignore_while(parser.all_formatting) - ks := key_str.split('.') - - if ks.len != 2 { + if dotted_key.len != 2 { return error(@MOD + '.' + @STRUCT + '.' + @FN + ' nested array of tables does not support more than 2 levels. (excerpt): "...${p.excerpt()}..."') } - first := ks[0] // The array that holds the entries - last := ks[1] // The key the parsed array data should be added to + first := DottedKey([dotted_key[0]]) // The array that holds the entries + last := DottedKey([dotted_key[1]]) // The key the parsed array data should be added to mut t_arr := &[]ast.Value(0) mut t_map := ast.Value(ast.Null{}) @@ -640,11 +674,11 @@ pub fn (mut p Parser) double_array_of_tables(mut table map[string]ast.Value) ? { // NOTE this is starting to get EVEN uglier. TOML is not at all simple at this point... if first != p.last_aot { // Implicit allocation - if p.last_aot == '' { - util.printdbg(@MOD + '.' + @STRUCT + '.' + @FN, 'implicit allocation of array for nested key `$key_str`.') - table[first] = []ast.Value{} + if p.last_aot.len == 0 { + util.printdbg(@MOD + '.' + @STRUCT + '.' + @FN, 'implicit allocation of array for dotted key `$dotted_key`.') + table[first.str()] = []ast.Value{} p.last_aot = first - t_arr = &(table[p.last_aot] as []ast.Value) + t_arr = &(table[p.last_aot.str()] as []ast.Value) t_arr << ast.Value(map[string]ast.Value{}) p.last_aot_index = t_arr.len - 1 } else { @@ -653,7 +687,7 @@ pub fn (mut p Parser) double_array_of_tables(mut table map[string]ast.Value) ? { } } - t_arr = &(table[p.last_aot] as []ast.Value) + t_arr = &(table[p.last_aot.str()] as []ast.Value) t_map = ast.Value(map[string]ast.Value{}) if p.last_aot_index < t_arr.len { t_map = t_arr[p.last_aot_index] @@ -661,17 +695,17 @@ pub fn (mut p Parser) double_array_of_tables(mut table map[string]ast.Value) ? { mut t := &(t_map as map[string]ast.Value) - if val := t[last] { + if val := t[last.str()] { if val is []ast.Value { arr := &(val as []ast.Value) - arr << p.double_array_of_tables_contents(key_str) ? - t[last] = arr + arr << p.double_array_of_tables_contents(dotted_key) ? + t[last.str()] = arr } else { return error(@MOD + '.' + @STRUCT + '.' + @FN + - ' t[$last] is not an array. (excerpt): "...${p.excerpt()}..."') + ' t[$last.str()] is not an array. (excerpt): "...${p.excerpt()}..."') } } else { - t[last] = p.double_array_of_tables_contents(key_str) ? + t[last.str()] = p.double_array_of_tables_contents(dotted_key) ? } if t_arr.len == 0 { t_arr << t @@ -681,11 +715,11 @@ pub fn (mut p Parser) double_array_of_tables(mut table map[string]ast.Value) ? { } // double_array_of_tables_contents parses next tokens into an array of `ast.Value`s. -pub fn (mut p Parser) double_array_of_tables_contents(target_key string) ?[]ast.Value { +pub fn (mut p Parser) double_array_of_tables_contents(target_key DottedKey) ?[]ast.Value { util.printdbg(@MOD + '.' + @STRUCT + '.' + @FN, 'parsing contents from "$p.tok.kind" "$p.tok.lit"') mut tbl := map[string]ast.Value{} - mut implicit_allocation_key := '' + mut implicit_allocation_key := DottedKey([]string{}) mut peeked_over := 0 mut peek_tok := p.peek_tok @@ -709,21 +743,20 @@ pub fn (mut p Parser) double_array_of_tables_contents(target_key string) ?[]ast. match p.tok.kind { .bare, .quoted, .boolean, .number { if p.peek_tok.kind == .period { - dotkey := p.dotted_key() ? + mut dotkey := p.dotted_key() ? p.ignore_while(parser.space_formatting) p.check(.assign) ? val := p.value() ? - mut implicit := '' - if implicit_allocation_key != '' { - implicit = implicit_allocation_key + '.' + if implicit_allocation_key.len > 0 { + dotkey.insert(0, implicit_allocation_key) } - sub_table, key := p.sub_table_key(implicit + dotkey) + sub_table, key := p.sub_table_key(dotkey) mut t := p.find_in_table(mut tbl, sub_table) ? unsafe { util.printdbg(@MOD + '.' + @STRUCT + '.' + @FN, 'inserting @6 "$key" = $val.to_json() into ${ptr_str(t)}') - t[key] = val + t[key.str()] = val } } else { key, val := p.key_value() ? @@ -732,7 +765,7 @@ pub fn (mut p Parser) double_array_of_tables_contents(target_key string) ?[]ast. unsafe { t = &tbl } - if implicit_allocation_key != '' { + if implicit_allocation_key.len > 0 { t = p.find_in_table(mut tbl, implicit_allocation_key) ? } unsafe { @@ -755,7 +788,10 @@ pub fn (mut p Parser) double_array_of_tables_contents(target_key string) ?[]ast. // Parse `[d.e.f]` p.ignore_while(parser.space_formatting) dotkey := p.dotted_key() ? - implicit_allocation_key = dotkey.all_after(target_key + '.') + implicit_allocation_key = dotkey + if dotkey.len > 2 { + implicit_allocation_key = dotkey[2..] + } p.ignore_while(parser.space_formatting) util.printdbg(@MOD + '.' + @STRUCT + '.' + @FN, 'keys are: dotted `$dotkey`, target `$target_key`, implicit `$implicit_allocation_key` at "$p.tok.kind" "$p.tok.lit"') p.expect(.rsbr) ? diff --git a/vlib/toml/tests/burntsushi.toml-test_test.v b/vlib/toml/tests/burntsushi.toml-test_test.v index 8e2de78770..7b60f39bea 100644 --- a/vlib/toml/tests/burntsushi.toml-test_test.v +++ b/vlib/toml/tests/burntsushi.toml-test_test.v @@ -13,7 +13,6 @@ const ( invalid_exceptions = [ // Table 'table/duplicate-table-array2.toml', - 'table/duplicate.toml', 'table/array-implicit.toml', 'table/injection-2.toml', 'table/injection-1.toml', @@ -40,7 +39,7 @@ fn test_burnt_sushi_tomltest() { relative = relative.replace('/', '\\') } if relative !in valid_exceptions { - println('OK [$i/$valid_test_files.len] "$valid_test_file"...') + println('OK [${i + 1}/$valid_test_files.len] "$valid_test_file"...') toml_doc := toml.parse_file(valid_test_file) or { panic(err) } // parsed_json := toml_doc.to_json().replace(' ','') @@ -51,7 +50,7 @@ fn test_burnt_sushi_tomltest() { valid++ } else { e++ - println('SKIP [$i/$valid_test_files.len] "$valid_test_file" EXCEPTION [$e/$valid_exceptions.len]...') + println('SKIP [${i + 1}/$valid_test_files.len] "$valid_test_file" EXCEPTION [$e/$valid_exceptions.len]...') } } println('$valid/$valid_test_files.len TOML files was parsed correctly') @@ -74,7 +73,7 @@ fn test_burnt_sushi_tomltest() { relative = relative.replace('/', '\\') } if relative !in invalid_exceptions { - println('OK [$i/$invalid_test_files.len] "$invalid_test_file"...') + println('OK [${i + 1}/$invalid_test_files.len] "$invalid_test_file"...') if toml_doc := toml.parse_file(invalid_test_file) { content_that_should_have_failed := os.read_file(invalid_test_file) or { panic(err) @@ -88,7 +87,7 @@ fn test_burnt_sushi_tomltest() { invalid++ } else { e++ - println('SKIP [$i/$invalid_test_files.len] "$invalid_test_file" EXCEPTION [$e/$invalid_exceptions.len]...') + println('SKIP [${i + 1}/$invalid_test_files.len] "$invalid_test_file" EXCEPTION [$e/$invalid_exceptions.len]...') } } println('$invalid/$invalid_test_files.len TOML files was parsed correctly') diff --git a/vlib/toml/tests/quoted_keys_test.v b/vlib/toml/tests/quoted_keys_test.v new file mode 100644 index 0000000000..506668eada --- /dev/null +++ b/vlib/toml/tests/quoted_keys_test.v @@ -0,0 +1,12 @@ +import toml + +fn test_quoted_keys() { + str_value := 'V rocks!' + toml_txt := 'a."b.c" = "V rocks!"' + toml_doc := toml.parse(toml_txt) or { panic(err) } + + value := toml_doc.value('a."b.c"') + assert value == toml.Any(str_value) + assert value as string == str_value + assert value.string() == str_value +} diff --git a/vlib/toml/toml.v b/vlib/toml/toml.v index ba102a465d..3c6a66f5d9 100644 --- a/vlib/toml/toml.v +++ b/vlib/toml/toml.v @@ -90,9 +90,31 @@ pub fn (d Doc) to_json() string { } // value queries a value from the TOML document. +// `key` should be in "dotted" form (`a.b.c`). +// `key` supports quoted keys like `a."b.c"`. pub fn (d Doc) value(key string) Any { values := d.ast.table as map[string]ast.Value - return d.get_map_value_as_any(values, key) + key_split := util.parse_dotted_key(key) or { return Any(Null{}) } + return d.value_(values, key_split) +} + +// value_ returns the value found at `key` in the map `values` as `Any` type. +fn (d Doc) value_(values map[string]ast.Value, key []string) Any { + util.printdbg(@MOD + '.' + @STRUCT + '.' + @FN, ' getting "${key[0]}"') + value := values[key[0]] or { + return Any(Null{}) + // TODO decide this + // panic(@MOD + '.' + @STRUCT + '.' + @FN + ' key "$key[0]" does not exist') + } + // `match` isn't currently very suitable for these types of sum type constructs... + if value is map[string]ast.Value { + if key.len <= 1 { + return d.ast_to_any(value) + } + m := (value as map[string]ast.Value) + return d.value_(m, key[1..]) + } + return d.ast_to_any(value) } // ast_to_any_value converts `from` ast.Value to toml.Any value. @@ -177,29 +199,3 @@ fn (d Doc) ast_to_any(value ast.Value) Any { // panic(@MOD + '.' + @STRUCT + '.' + @FN + ' can\'t convert "$value"') // return Any('') } - -// get_map_value_as_any returns the value found at `key` in the map `values` as `Any` type. -fn (d Doc) get_map_value_as_any(values map[string]ast.Value, key string) Any { - key_split := key.split('.') - util.printdbg(@MOD + '.' + @STRUCT + '.' + @FN, ' getting "${key_split[0]}"') - if key_split[0] in values.keys() { - value := values[key_split[0]] or { - return Any(Null{}) - // TODO decide this - // panic(@MOD + '.' + @STRUCT + '.' + @FN + ' key "$key" does not exist') - } - // `match` isn't currently very suitable for these types of sum type constructs... - if value is map[string]ast.Value { - m := (value as map[string]ast.Value) - next_key := key_split[1..].join('.') - if next_key == '' { - return d.ast_to_any(value) - } - return d.get_map_value_as_any(m, next_key) - } - return d.ast_to_any(value) - } - return Any(Null{}) - // TODO decide this - // panic(@MOD + '.' + @STRUCT + '.' + @FN + ' key "$key" does not exist') -} diff --git a/vlib/toml/util/util.v b/vlib/toml/util/util.v index 60f42c8b05..6381197ca9 100644 --- a/vlib/toml/util/util.v +++ b/vlib/toml/util/util.v @@ -25,3 +25,44 @@ pub fn is_illegal_ascii_control_character(byte_char byte) bool { pub fn printdbg(id string, message string) { eprintln(id + ' ' + message) } + +// parse_dotted_key converts `key` string to an array of strings. +// parse_dotted_key preserves strings delimited by both `"` and `'`. +pub fn parse_dotted_key(key string) ?[]string { + mut out := []string{} + mut buf := '' + mut in_string := false + mut delim := byte(` `) + for ch in key { + if ch in [`"`, `'`] { + if !in_string { + delim = ch + } + in_string = !in_string && ch == delim + if !in_string { + if buf != '' && buf != ' ' { + out << buf + } + buf = '' + delim = ` ` + } + continue + } + buf += ch.ascii_str() + if !in_string && ch == `.` { + if buf != '' && buf != ' ' { + out << buf[..buf.len - 1] + } + buf = '' + continue + } + } + if buf != '' && buf != ' ' { + out << buf + } + if in_string { + return error(@FN + + ': could not parse key, missing closing string delimiter `$delim.ascii_str()`') + } + return out +}