///|
fn switch_error(message : String, code : String) -> Unit raise TclError {
raise Signal(completion_error(message, errorcode=code))
}
///|
fn Interpreter::switch_command(
self : Interpreter,
values : Array[TclValue],
depth : Int,
discard_result? : Bool = false,
) -> TclValue raise TclError {
let n = values.length()
if n < 3 {
switch_error(
"wrong # args: should be \"switch ?-option ...? string ?pattern body ...? ?default body?\"",
"TCL WRONGARGS",
)
}
let mut at = 1
let mut mode = ""
let mut nocase = false
let mut matchvar : String? = None
let mut indexvar : String? = None
let choices = [
"-exact", "-glob", "-indexvar", "-matchvar", "-nocase", "-regexp", "--",
]
while at < n - 2 && values[at].text.has_prefix("-") {
let option = values[at].text
let selected = select_keyword(option, choices) catch {
_ => {
switch_error(
"bad option \"" +
option +
"\": must be -exact, -glob, -indexvar, -matchvar, -nocase, -regexp, or --",
format_list(["TCL", "LOOKUP", "INDEX", "option", option]),
)
""
}
}
at += 1
match selected {
"--" => break
"-exact" | "-glob" | "-regexp" => {
if !mode.is_empty() {
switch_error(
"bad option \"" + option + "\": " + mode + " option already found",
"TCL OPERATION SWITCH DOUBLEOPT",
)
}
mode = selected
}
"-nocase" => nocase = true
"-matchvar" | "-indexvar" => {
if at >= n - 2 {
switch_error(
"missing variable name argument to " + selected + " option",
"TCL OPERATION SWITCH NOVAR",
)
}
if selected == "-matchvar" {
matchvar = Some(values[at].text)
} else {
indexvar = Some(values[at].text)
}
at += 1
}
_ => ()
}
}
if mode != "-regexp" && (matchvar is Some(_) || indexvar is Some(_)) {
switch_error(
(if indexvar is Some(_) { "-indexvar" } else { "-matchvar" }) +
" option requires -regexp option",
"TCL OPERATION SWITCH MODERESTRICTION",
)
}
let usage = "wrong # args: should be \"switch ?-option ...? string {?pattern body ...? ?default body?}\""
if at >= n - 1 {
switch_error(usage, "TCL WRONGARGS")
}
let subject = values[at]
at += 1
let arms = if at == n - 1 {
values[at].as_list()
} else {
values[at:].to_owned()
}
if arms.is_empty() {
switch_error(usage, "TCL WRONGARGS")
}
if arms.length() % 2 != 0 {
switch_error(
"extra switch pattern with no body", "TCL OPERATION SWITCH BADARM",
)
}
if arms[arms.length() - 1].text == "-" {
switch_error(
"no body specified for pattern \"" + arms[arms.length() - 2].text + "\"",
"TCL OPERATION SWITCH BADARM FALLTHROUGH",
)
}
let mut i = 0
while i < arms.length() {
self.tick()
let pattern = arms[i].text
let fallback = i == arms.length() - 2 && pattern == "default"
let mut captures : Array[(Int, Int)] = []
let matched = if fallback {
true
} else if mode == "-regexp" {
match self.regexp_matches(arms[i], subject.text, nocase) {
Some(result) => {
captures = result
true
}
None => false
}
} else if mode == "-glob" {
glob_match(pattern, subject.text, nocase)
} else if nocase {
unicode_case(pattern, 0) == unicode_case(subject.text, 0)
} else {
pattern == subject.text
}
if matched {
// Native Tcl writes indices first, then substrings, including when both
// options name the same variable. A failing first write stops the second.
if indexvar is Some(name) {
self.set_value(
name,
list_value(
captures.map(pair => {
list_value([
text_value((if pair.1 == 0 { -1 } else { pair.0 }).to_string()),
text_value(
(if pair.1 < 0 { -1 } else { pair.1 - 1 }).to_string(),
),
])
}),
),
)
}
if matchvar is Some(name) {
self.set_value(
name,
list_value(
captures.map(pair => {
text_value(
if pair.0 < 0 {
""
} else {
unit_slice(subject.text, pair.0, pair.1)
},
)
}),
),
)
}
let mut body = i + 1
while arms[body].text == "-" {
body += 2
}
return self.execute_value_script(arms[body], depth + 1, discard_result~) catch {
error => {
let result = outcome(error)
if result.actual_code() == 1 {
let options = result.options.copy()
let line = option_get(options, "-errorline").unwrap_or("1")
let info = option_get(options, "-errorinfo").unwrap_or(
result.value.text,
)
option_set(
options,
"-errorinfo",
info + "\n (\"" + pattern + "\" arm line " + line + ")",
)
raise Signal({ ..result, options, })
}
raise Signal(result)
}
}
}
i += 2
}
text_value("")
}