///|
fn Interpreter::dict_command(
self : Interpreter,
input : Array[TclValue],
depth : Int,
discard_result? : Bool = false,
object_path? : Bool = true,
) -> TclValue raise TclError {
let args = input.map(v => v.text)
let n = args.length()
if n < 2 {
raise Invalid("dict arity")
}
if object_path &&
(
(args[1] == "set" && n == 5) ||
(args[1] == "incr" && (n == 4 || n == 5)) ||
(["append", "lappend"].contains(args[1]) && n >= 4)
) {
return self.update_dictionary(input, depth, discard_result)
}
let text = match args[1] {
"create" =>
return dictionary_value(
dictionary_values(list_value(input[2:].to_owned())),
)
"get" => {
if n < 3 {
raise Invalid("dict get arity")
}
if n == 3 {
return dictionary_value(dictionary_values(input[2]))
} else {
return dictionary_get_value(input[2], input[3:].to_owned())
}
}
"exists" => {
if n < 4 {
raise Invalid("dict exists arity")
}
ignore(dictionary_values(input[2]))
boolean_text(
try {
ignore(dictionary_get_value(input[2], input[3:].to_owned()))
true
} catch {
_ => false
},
)
}
"size" => {
if n != 3 {
raise Invalid("dict size arity")
}
dictionary_values(input[2]).length().to_string()
}
"keys" | "values" => {
if n != 3 && n != 4 {
raise Invalid("dict keys/values arity")
}
let result = []
for (key, value) in dictionary_values(input[2]) {
let item = if args[1] == "keys" { key } else { value }
if n == 3 || glob_match(args[3], item.text, false) {
result.push(item)
}
}
return list_value(result)
}
"merge" => {
let mut result = text_value("")
for value in input[2:] {
for (key, value) in dictionary_values(value) {
result = dictionary_edit_value(result, [key], Some(value), 0)
}
}
return result
}
"replace" | "remove" => {
if n < 3 {
raise Invalid("dict replace/remove arity")
}
let mut result = dictionary_value(dictionary_values(input[2]))
if args[1] == "replace" {
if (n - 3) % 2 != 0 {
raise Invalid("dict replace expects pairs")
}
let mut i = 3
while i < n {
result = dictionary_edit_value(
result,
[input[i]],
Some(input[i + 1]),
0,
)
i += 2
}
} else {
for key in input[3:] {
result = dictionary_edit_value(result, [key], None, 0)
}
}
return result
}
"set" | "unset" => {
if n < (if args[1] == "set" { 5 } else { 4 }) {
raise Invalid("dict set/unset arity")
}
let last = if args[1] == "set" { n - 1 } else { n }
let previous = if args[1] == "set" {
self.get_value(args[2]).unwrap_or(text_value(""))
} else {
self.read_value(args[2])
}
let result = dictionary_edit_value(
previous,
input[3:last].to_owned(),
if args[1] == "set" {
Some(input[n - 1])
} else {
None
},
0,
)
self.set_value(args[2], result)
return result
}
"incr" | "append" | "lappend" => {
if n < 4 || (args[1] == "incr" && n > 5) {
raise Invalid("dict update arity")
}
let previous = self.get_value(args[2]).unwrap_or(text_value(""))
ignore(dictionary_values(previous))
let current = dictionary_get_value(previous, [input[3]]) catch {
_ => text_value("")
}
let value = if args[1] == "incr" {
let exists = dictionary_values(previous)
.iter()
.any(p => p.0.text == args[3])
text_value(
integer_add(
if exists {
current.text
} else {
"0"
},
if n == 5 {
args[4]
} else {
"1"
},
),
)
} else if args[1] == "append" {
text_value(current.text + args[4:].to_owned().join(""))
} else {
list_value(current.as_list() + input[4:].to_owned())
}
if value.text.length() > 1000000 {
raise Invalid("dictionary value size")
}
let result = dictionary_edit_value(previous, [input[3]], Some(value), 0)
self.set_value(args[2], result)
return result
}
"for" | "map" => {
if n != 5 {
raise Invalid("dict for/map arity")
}
let names = parse_list(args[2])
if names.length() != 2 {
raise Invalid("dict loop requires two variables")
}
let result = []
for (key, value) in dictionary_values(input[3]) {
self.tick()
self.set_value(names[0], key)
self.set_value(names[1], value)
try {
let computed = self.execute_value_script(input[4], depth + 1)
if args[1] == "map" {
result.push((self.read_value(names[0]), computed))
}
} catch {
Continue => continue
Break => break
Signal(result) if result.actual_code() == 4 => continue
Signal(result) if result.actual_code() == 3 => break
error => raise error
}
}
if args[1] == "map" {
return dictionary_value(dictionary_values(dictionary_value(result)))
} else {
""
}
}
"filter" => {
if n < 5 {
raise Invalid("dict filter arity")
}
let result = []
for (key, value) in dictionary_values(input[2]) {
self.tick()
let keep = if args[3] == "key" || args[3] == "value" {
let item = if args[3] == "key" { key } else { value }
let mut matched = false
for pattern in args[4:] {
if glob_match(pattern, item.text, false) {
matched = true
break
}
}
matched
} else if args[3] == "script" && n == 6 {
let names = parse_list(args[4])
if names.length() != 2 {
raise Invalid("dict filter variable list")
}
self.set_value(names[0], key)
self.set_value(names[1], value)
self.execute_value_script(input[5], depth + 1).truth() catch {
Continue => continue
Break => break
Signal(result) if result.actual_code() == 4 => continue
Signal(result) if result.actual_code() == 3 => break
error => raise error
}
} else {
raise Invalid("dict filter mode")
}
if keep {
result.push((key, value))
}
}
return dictionary_value(result)
}
"update" => {
if n < 6 || n % 2 != 0 {
raise Invalid("dict update arity")
}
let original = self.read_value(args[2])
ignore(dictionary_values(original))
let mut i = 3
while i < n - 1 {
if (Some(dictionary_get_value(original, [input[i]])) catch {
_ => None
})
is Some(value) {
self.set_value(args[i + 1], value)
} else {
self.unset_var(args[i + 1], true)
}
i += 2
}
defer (if self.get_value(args[2]) is Some(current) {
let mut updated = current
let mut i = 3
while i < n - 1 {
updated = dictionary_edit_value(
updated,
[input[i]],
self.get_value(args[i + 1]),
0,
)
i += 2
}
self.set_value(args[2], updated)
})
return self.execute_value_script(input[n - 1], depth + 1)
}
"with" => {
if n < 4 {
raise Invalid("dict with arity")
}
let original = self.read_value(args[2])
let keys = input[3:n - 1].to_owned()
let selected = dictionary_get_value(original, keys)
let pairs = dictionary_values(selected)
for (key, value) in pairs {
self.set_value(key.text, value)
}
defer (if self.get_value(args[2]) is Some(current) {
let mut updated = dictionary_get_value(current, keys)
for (key, _) in pairs {
updated = dictionary_edit_value(
updated,
[key],
self.get_value(key.text),
0,
)
}
self.set_value(
args[2],
if keys.is_empty() {
updated
} else {
dictionary_edit_value(current, keys, Some(updated), 0)
},
)
})
return self.execute_value_script(input[n - 1], depth + 1)
}
_ => raise Invalid("unsupported dict subcommand")
}
text_value(text)
}
///|
fn dictionary_values(
text : TclValue,
) -> Array[(TclValue, TclValue)] raise TclError {
if text.payload is Pairs(pairs) {
return pairs.copy()
}
let words = text.as_list()
if words.length() % 2 != 0 {
raise Invalid("missing dictionary value")
}
let result = []
let seen : Map[String, Int] = Map([])
let mut i = 0
while i < words.length() {
if seen.get(words[i].text) is Some(index) {
result[index] = (result[index].0, words[i + 1])
} else {
seen[words[i].text] = result.length()
result.push((words[i], words[i + 1]))
}
i += 2
}
text.payload = Pairs(result.copy())
result
}
///|
fn dictionary_value(
pairs : Array[(TclValue, TclValue)],
) -> TclValue raise TclError {
let values = []
for (key, value) in pairs {
values.push(key)
values.push(value)
}
{ text: format_list(values.map(v => v.text)), payload: Pairs(pairs.copy()), }
}
///|
fn dictionary_get_value(
text : TclValue,
path : Array[TclValue],
) -> TclValue raise TclError {
let mut value = text
for key in path {
let pairs = dictionary_values(value)
let found = pairs.filter(p => p.0.text == key.text).get(0)
value = match found {
Some((_, v)) => v
None => raise Invalid("dictionary key not found")
}
}
value
}
///|
fn dictionary_edit_value(
text : TclValue,
keys : Array[TclValue],
value : TclValue?,
depth : Int,
) -> TclValue raise TclError {
if depth > 64 {
raise Invalid("dictionary nesting limit")
}
if keys.is_empty() {
raise Invalid("dictionary key required")
}
let pairs = dictionary_values(text)
let mut index = -1
for i in 0.. 1 {
if index < 0 && value is None {
raise Invalid("dictionary path not found")
}
Some(
dictionary_edit_value(
if index < 0 {
text_value("")
} else {
pairs[index].1
},
keys[1:].to_owned(),
value,
depth + 1,
),
)
} else {
value
}
match value {
Some(v) =>
if index < 0 {
pairs.push((keys[0], v))
} else {
pairs[index] = (pairs[index].0, v)
}
None => if index >= 0 { ignore(pairs.remove(index)) }
}
dictionary_value(pairs)
}
///|