mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
toml: support for [a."b.c"]
quoted keys (#12444)
This commit is contained in:
parent
6c32c544e1
commit
9c508237bd
@ -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')
|
||||
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')
|
||||
}
|
||||
// `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)
|
||||
next_key := key_split[1..].join('.')
|
||||
if next_key == '' {
|
||||
return value
|
||||
}
|
||||
return nm.value(next_key)
|
||||
return nm.value_(key[1..])
|
||||
}
|
||||
return value
|
||||
}
|
||||
return error(@MOD + '.' + @STRUCT + '.' + @FN + ' key "$key" does not exist')
|
||||
}
|
||||
|
||||
pub fn (a []Any) as_strings() []string {
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
@ -27,9 +42,10 @@ mut:
|
||||
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_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) ?
|
||||
|
@ -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')
|
||||
|
12
vlib/toml/tests/quoted_keys_test.v
Normal file
12
vlib/toml/tests/quoted_keys_test.v
Normal file
@ -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
|
||||
}
|
@ -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')
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user