molt_forked/
commands.rs

1//! # Standard Molt Command Definitions
2//!
3//! This module defines the standard Molt commands.
4
5use crate::{
6    dict::{dict_new, dict_path_insert, dict_path_remove, list_to_dict},
7    interp::Interp,
8    types::*,
9    util, *,
10};
11use std::fs;
12cfg_if::cfg_if! {
13  if #[cfg(feature = "wasm")] {
14    use wasm_timer::Instant;
15  }else{
16    use std::time::Instant;
17  }
18}
19
20pub const _APPEND: &str = "append";
21pub const _ARRAY: &str = "array";
22pub const _ASSERT_EQ: &str = "assert_eq";
23pub const _BREAK: &str = "break";
24pub const _CATCH: &str = "catch";
25pub const _CONTINUE: &str = "continue";
26pub const _DICT: &str = "dict";
27pub const _ERROR: &str = "error";
28pub const _EXPR: &str = "expr";
29pub const _FOR: &str = "for";
30pub const _FOREACH: &str = "foreach";
31pub const _GLOBAL: &str = "global";
32pub const _IF: &str = "if";
33pub const _INCR: &str = "incr";
34pub const _INFO: &str = "info";
35pub const _JOIN: &str = "join";
36pub const _LAPPEND: &str = "lappend";
37pub const _LINDEX: &str = "lindex";
38pub const _LIST: &str = "list";
39pub const _LLENGTH: &str = "llength";
40pub const _PROC: &str = "proc";
41pub const _PUTS: &str = "puts";
42pub const _RENAME: &str = "rename";
43pub const _RETURN: &str = "return";
44pub const _SET: &str = "set";
45pub const _STRING: &str = "string";
46pub const _THROW: &str = "throw";
47pub const _TIME: &str = "time";
48pub const _UNSET: &str = "unset";
49pub const _WHILE: &str = "while";
50pub const _SOURCE: &str = "source";
51pub const _EXIT: &str = "exit";
52pub const _PARSE: &str = "parse";
53pub const _PDUMP: &str = "pdump";
54pub const _PCLEAR: &str = "pclear";
55
56/// # append *varName* ?*value* ...?
57///
58/// Appends one or more strings to a variable.
59/// See molt-book for full semantics.
60pub fn cmd_append<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
61    check_args(1, argv, 2, 0, "varName ?value value ...?")?;
62
63    // FIRST, get the value of the variable.  If the variable is undefined,
64    // start with the empty string.
65    let mut new_string: String = interp
66        .var(&argv[1])
67        .and_then(|val| Ok(val.to_string()))
68        .unwrap_or_else(|_| String::new());
69
70    // NEXT, append the remaining values to the string.
71    for item in &argv[2..] {
72        new_string.push_str(item.as_str());
73    }
74
75    // NEXT, save and return the new value.
76    interp.set_var_return(&argv[1], new_string.into())
77}
78
79/// # array *subcommand* ?*arg*...?
80///
81/// https://www.tcl.tk/man/tcl8.6/TclCmd/array.htm
82pub fn cmd_array<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
83    // cfg_if::cfg_if! {
84    //     if #[cfg(feature = "native_subcmd_help")] {
85    //         let f = gen_subcommand!(
86    //             Ctx,
87    //             1,
88    //             [
89    //                 ("anymore", "    ", cmd_todo, "[TODO] array anymore arrayName searchId"),
90    //                 ("donesearch", " ", cmd_todo, "[TODO] array donesearch arrayName searchId"),
91    //                 ("exists", "     ", cmd_array_exists,"array exists arrayName"),
92    //                 ("get", "        ", cmd_array_get,   "array get arrayName ?pattern?"),
93    //                 ("names", "      ", cmd_array_names, "array names arrayName ?mode? ?pattern?"),
94    //                 ("nextelement", "", cmd_todo, "[TODO] array nextelement arrayName searchId"),
95    //                 ("set", "        ", cmd_array_set,   "array set arrayName list"),
96    //                 ("size", "       ", cmd_array_size,  "array size arrayName"),
97    //                 ("startsearch", "", cmd_todo, "[TODO] array startsearch arrayName"),
98    //                 ("statistics", " ", cmd_todo, "[TODO] array statistics arrayName"),
99    //                 ("unset", "      ", cmd_array_unset, "array unset arrayName ?pattern?"),
100    //             ],
101    //         );
102    //     }else{
103    let f = _gen_subcommand_generic!(
104        1,
105        [
106            ("exists", cmd_array_exists),
107            ("get", cmd_array_get),
108            ("names", cmd_array_names),
109            ("set", cmd_array_set),
110            ("size", cmd_array_size),
111            ("unset", cmd_array_unset),
112        ],
113    );
114    f(interp, argv)
115}
116
117/// # array exists arrayName
118pub fn cmd_array_exists<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
119    check_args(2, argv, 3, 3, "arrayName")?;
120    molt_ok!(Value::from(interp.array_exists(argv[2].as_str())))
121}
122
123/// # array names arrayName
124/// TODO: Add glob matching as a feature, and support standard TCL options.
125pub fn cmd_array_names<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
126    check_args(2, argv, 3, 3, "arrayName")?;
127    molt_ok!(Value::from(interp.array_names(argv[2].as_str())))
128}
129
130/// # array get arrayname
131/// TODO: Add glob matching as a feature, and support standard TCL options.
132pub fn cmd_array_get<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
133    check_args(2, argv, 3, 3, "arrayName")?;
134    molt_ok!(Value::from(interp.array_get(argv[2].as_str())))
135}
136
137/// # parse *script*
138///
139/// A command for parsing an arbitrary script and outputting the parsed form.
140/// This is an undocumented debugging aid.  The output can be greatly improved.
141pub fn cmd_parse<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
142    check_args(1, argv, 2, 2, "script")?;
143
144    let script = &argv[1];
145
146    molt_ok!(format!("{:?}", parser::parse(script.as_str())?))
147}
148
149/// # array set arrayName list
150pub fn cmd_array_set<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
151    check_args(2, argv, 4, 4, "arrayName list")?;
152
153    // This odd little dance provides the same semantics as Standard TCL.  If the
154    // given var_name has an index, the array is created (if it didn't exist)
155    // but no data is added to it, and the command returns an error.
156    let var_name = argv[2].as_var_name();
157
158    if var_name.index().is_none() {
159        interp.array_set(var_name.name(), &*argv[3].as_list()?)
160    } else {
161        // This line will create the array if it doesn't exist, and throw an error if the
162        // named variable exists but isn't an array.  This is a little wacky, but it's
163        // what TCL 8.6 does.
164        interp.array_set(var_name.name(), &*Value::empty().as_list()?)?;
165
166        // And this line throws an error because the full name the caller specified is an
167        // element, not the array itself.
168        molt_err!("can't set \"{}\": variable isn't array", &argv[2])
169    }
170}
171
172/// # array size arrayName
173pub fn cmd_array_size<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
174    check_args(2, argv, 3, 3, "arrayName")?;
175    molt_ok!(Value::from(interp.array_size(argv[2].as_str()) as MoltInt))
176}
177
178/// # array unset arrayName ?*index*?
179pub fn cmd_array_unset<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
180    check_args(2, argv, 3, 4, "arrayName ?index?")?;
181
182    if argv.len() == 3 {
183        interp.array_unset(argv[2].as_str());
184    } else {
185        interp.unset_element(argv[2].as_str(), argv[3].as_str());
186    }
187    molt_ok!()
188}
189
190/// assert_eq received, expected
191///
192/// Asserts that two values have identical string representations.
193/// See molt-book for full semantics.
194pub fn cmd_assert_eq<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
195    check_args(1, argv, 3, 3, "received expected")?;
196
197    if argv[1] == argv[2] {
198        molt_ok!()
199    } else {
200        molt_err!("assertion failed: received \"{}\", expected \"{}\".", argv[1], argv[2])
201    }
202}
203
204/// # break
205///
206/// Breaks a loops.
207/// See molt-book for full semantics.
208pub fn cmd_break<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
209    check_args(1, argv, 1, 1, "")?;
210
211    Err(Exception::molt_break())
212}
213
214/// catch script ?resultVarName? ?optionsVarName?
215///
216/// Executes a script, returning the result code.  If the resultVarName is given, the result
217/// of executing the script is returned in it.  The result code is returned as an integer,
218/// 0=Ok, 1=Error, 2=Return, 3=Break, 4=Continue.
219pub fn cmd_catch<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
220    check_args(1, argv, 2, 4, "script ?resultVarName? ?optionsVarName?")?;
221
222    // If the script called `return x`, should get Return, -level 1, -code Okay here
223    let result = interp.eval_value(&argv[1]);
224
225    let (code, value) = match &result {
226        Ok(val) => (0, val.clone()),
227        Err(exception) => match exception.code() {
228            ResultCode::Okay => unreachable!(), // Should not be reachable here.
229            ResultCode::Error => (1, exception.value()),
230            ResultCode::Return => (2, exception.value()),
231            ResultCode::Break => (3, exception.value()),
232            ResultCode::Continue => (4, exception.value()),
233            ResultCode::Other(_) => unimplemented!(), // TODO: Not in use yet
234        },
235    };
236
237    if argv.len() >= 3 {
238        interp.set_var(&argv[2], value)?;
239    }
240
241    if argv.len() == 4 {
242        interp.set_var(&argv[3], interp.return_options(&result))?;
243    }
244
245    Ok(Value::from(code))
246}
247
248/// # continue
249///
250/// Continues with the next iteration of the inmost loop.
251pub fn cmd_continue<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
252    check_args(1, argv, 1, 1, "")?;
253
254    Err(Exception::molt_continue())
255}
256
257/// # dict *subcommand* ?*arg*...?
258///
259/// https://www.tcl.tk/man/tcl8.6/TclCmd/dict.htm
260pub fn cmd_dict<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
261    // cfg_if::cfg_if! {
262    //     if #[cfg(feature = "native_subcmd_help")] {
263    //         let f = gen_subcommand!(
264    //             Ctx,
265    //             1,
266    //             [
267    //                 ("append", " ", cmd_todo, "[TODO] dict append dictionaryVariable key ?string ...?"),
268    //                 ("create", " ", cmd_dict_new,"dict create ?key value ...?"),
269    //                 ("exists", " ", cmd_dict_exists,"dict exists dictionaryValue key ?key ...?"),
270    //                 ("filter", " ", cmd_todo, "[TODO] dict filter dictionaryValue filterType arg ?arg ...?"),
271    //                 // dict filter dictionaryValue key ?globPattern ...?
272    //                 // dict filter dictionaryValue script {keyVariable valueVariable} script
273    //                 // dict filter dictionaryValue value ?globPattern ...?
274    //                 ("for", "    ", cmd_todo, "[TODO] dict for {keyVariable valueVariable} dictionaryValue body"),
275    //                 ("get", "    ", cmd_dict_get,"dict get dictionaryValue ?key ...?"),
276    //                 ("incr", "   ", cmd_todo,"[TODO] dict incr dictionaryVariable key ?increment?"),
277    //                 ("info", "   ", cmd_todo,"[TODO] dict info dictionaryValue"),
278    //                 ("keys", "   ", cmd_dict_keys,"dict keys dictionaryValue ?globPattern?"),
279    //                 ("lappend", "", cmd_todo,"[TODO] dict lappend dictionaryVariable key ?value ...?"),
280    //                 ("map", "    ", cmd_todo,"[TODO] dict map {keyVariable valueVariable} dictionaryValue body"),
281    //                 ("merge", "  ", cmd_todo,"[TODO] dict merge ?dictionaryValue ...?"),
282    //                 ("remove", " ", cmd_dict_remove,"dict remove dictionaryValue ?key ...?"),
283    //                 ("replace", "", cmd_todo,"[TODO] dict replace dictionaryValue ?key value ...?"),
284    //                 ("set", "    ", cmd_dict_set,"dict set dictionaryVariable key ?key ...? value"),
285    //                 ("size", "   ", cmd_dict_size,"dict size dictionaryValue"),
286    //                 ("unset", "  ", cmd_dict_unset,"dict unset dictionaryVariable key ?key ...?"),
287    //                 ("update", " ", cmd_todo,"[TODO] dict update dictionaryVariable key varName ?key varName ...? body"),
288    //                 ("values", " ", cmd_dict_values,"dict values dictionaryValue ?globPattern?"),
289    //                 ("with", "   ", cmd_todo,"[TODO] dict with dictionaryVariable ?key ...? body"),
290    //             ],
291    //         );
292    //     }else{
293    let f = _gen_subcommand_generic!(
294        1,
295        [
296            ("create", cmd_dict_new),
297            ("exists", cmd_dict_exists),
298            ("get", cmd_dict_get),
299            ("keys", cmd_dict_keys),
300            ("remove", cmd_dict_remove),
301            ("set", cmd_dict_set),
302            ("size", cmd_dict_size),
303            ("unset", cmd_dict_unset),
304            ("values", cmd_dict_values),
305        ],
306    );
307
308    //     }
309    // }
310    f(interp, argv)
311}
312
313/// # dict create ?key value ...?
314fn cmd_dict_new<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
315    // FIRST, we need an even number of arguments.
316    if argv.len() % 2 != 0 {
317        return molt_err!(
318            "wrong # args: should be \"{} {}\"",
319            Value::from(&argv[0..2]).to_string(),
320            "?key value?"
321        );
322    }
323
324    // NEXT, return the value.
325    if argv.len() > 2 {
326        molt_ok!(Value::from(list_to_dict(&argv[2..])))
327    } else {
328        molt_ok!(Value::from(dict_new()))
329    }
330}
331
332/// # dict exists *dictionary* key ?*key* ...?
333fn cmd_dict_exists<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
334    check_args(2, argv, 4, 0, "dictionary key ?key ...?")?;
335
336    let mut value: Value = argv[2].clone();
337    let indices = &argv[3..];
338
339    for index in indices {
340        if let Ok(dict) = value.as_dict() {
341            if let Some(val) = dict.get(index) {
342                value = val.clone();
343            } else {
344                return molt_ok!(false);
345            }
346        } else {
347            return molt_ok!(false);
348        }
349    }
350
351    molt_ok!(true)
352}
353
354/// # dict get *dictionary* ?*key* ...?
355fn cmd_dict_get<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
356    check_args(2, argv, 3, 0, "dictionary ?key ...?")?;
357
358    let mut value: Value = argv[2].clone();
359    let indices = &argv[3..];
360
361    for index in indices {
362        let dict = value.as_dict()?;
363
364        if let Some(val) = dict.get(index) {
365            value = val.clone();
366        } else {
367            return molt_err!("key \"{}\" not known in dictionary", index);
368        }
369    }
370
371    molt_ok!(value)
372}
373
374/// # dict keys *dictionary*
375/// TODO: Add filtering when we have glob matching.
376fn cmd_dict_keys<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
377    check_args(2, argv, 3, 3, "dictionary")?;
378
379    let dict = argv[2].as_dict()?;
380    let keys: MoltList = dict.keys().cloned().collect();
381    molt_ok!(keys)
382}
383
384/// # dict remove *dictionary* ?*key* ...?
385fn cmd_dict_remove<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
386    check_args(2, argv, 3, 0, "dictionary ?key ...?")?;
387
388    // FIRST, get and clone the dictionary, so we can modify it.
389    let mut dict = (&*argv[2].as_dict()?).clone();
390
391    // NEXT, remove the given keys.
392    for key in &argv[3..] {
393        // shift_remove preserves the order of the keys.
394        dict.shift_remove(key);
395    }
396
397    // NEXT, return it as a new Value.
398    molt_ok!(dict)
399}
400
401/// # dict set *dictVarName* *key* ?*key* ...? *value*
402fn cmd_dict_set<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
403    check_args(2, argv, 5, 0, "dictVarName key ?key ...? value")?;
404
405    let value = &argv[argv.len() - 1];
406    let keys = &argv[3..(argv.len() - 1)];
407
408    if let Ok(old_dict_val) = interp.var(&argv[2]) {
409        interp.set_var_return(&argv[2], dict_path_insert(&old_dict_val, keys, value)?)
410    } else {
411        let new_val = Value::from(dict_new());
412        interp.set_var_return(&argv[2], dict_path_insert(&new_val, keys, value)?)
413    }
414}
415
416/// # dict size *dictionary*
417fn cmd_dict_size<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
418    check_args(2, argv, 3, 3, "dictionary")?;
419
420    let dict = argv[2].as_dict()?;
421    molt_ok!(dict.len() as MoltInt)
422}
423
424/// # dict unset *dictVarName* *key* ?*key* ...?
425fn cmd_dict_unset<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
426    check_args(2, argv, 4, 0, "dictVarName key ?key ...?")?;
427
428    let keys = &argv[3..];
429
430    if let Ok(old_dict_val) = interp.var(&argv[2]) {
431        interp.set_var_return(&argv[2], dict_path_remove(&old_dict_val, keys)?)
432    } else {
433        let new_val = Value::from(dict_new());
434        interp.set_var_return(&argv[2], dict_path_remove(&new_val, keys)?)
435    }
436}
437
438/// # dict values *dictionary*
439/// TODO: Add filtering when we have glob matching.
440fn cmd_dict_values<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
441    check_args(2, argv, 3, 3, "dictionary")?;
442
443    let dict = argv[2].as_dict()?;
444    let values: MoltList = dict.values().cloned().collect();
445    molt_ok!(values)
446}
447
448/// error *message*
449///
450/// Returns an error with the given message.
451///
452/// ## TCL Liens
453///
454/// * In Standard TCL, `error` can optionally set the stack trace and an error code.
455pub fn cmd_error<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
456    check_args(1, argv, 2, 2, "message")?;
457
458    molt_err!(argv[1].clone())
459}
460
461/// # exit ?*returnCode*?
462///
463/// Terminates the application by calling `std::process::exit()`.
464/// If given, _returnCode_ must be an integer return code; if absent, it
465/// defaults to 0.
466pub fn cmd_exit<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
467    check_args(1, argv, 1, 2, "?returnCode?")?;
468
469    let return_code: MoltInt = if argv.len() == 1 { 0 } else { argv[1].as_int()? };
470
471    std::process::exit(return_code as i32)
472}
473
474/// # expr expr
475///
476/// Evaluates an expression and returns its result.
477///
478/// ## TCL Liens
479///
480/// See the Molt Book.
481
482pub fn cmd_expr<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
483    check_args(1, argv, 2, 0, "expr")?;
484
485    if argv.len() == 2 {
486        interp.expr(&argv[1])
487    } else {
488        let values = argv[1..].iter().map(|v| v.as_str()).collect::<Vec<_>>();
489        interp.expr(&Value::from(values.join(" ")))
490    }
491}
492
493/// # for *start* *test* *next* *command*
494///
495/// A standard "for" loop.  start, next, and command are scripts; test is an expression
496///
497pub fn cmd_for<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
498    check_args(1, argv, 5, 5, "start test next command")?;
499
500    let start = &argv[1];
501    let test = &argv[2];
502    let next = &argv[3];
503    let command = &argv[4];
504
505    // Start
506    interp.eval_value(start)?;
507
508    while interp.expr_bool(test)? {
509        let result = interp.eval_value(command);
510
511        if let Err(exception) = result {
512            match exception.code() {
513                ResultCode::Break => break,
514                ResultCode::Continue => (),
515                _ => return Err(exception),
516            }
517        }
518
519        // Execute next script.  Break is allowed, but continue is not.
520        let result = interp.eval_value(next);
521
522        if let Err(exception) = result {
523            match exception.code() {
524                ResultCode::Break => break,
525                ResultCode::Continue => {
526                    return molt_err!("invoked \"continue\" outside of a loop");
527                }
528                _ => return Err(exception),
529            }
530        }
531    }
532
533    molt_ok!()
534}
535
536/// # foreach *varList* *list* *body*
537///
538/// Loops over the items the list, assigning successive items to the variables in the
539/// *varList* and calling the *body* as a script once for each set of assignments.
540/// On the last iteration, the second and subsequents variables in the *varList* will
541/// be assigned the empty string if there are not enough list elements to fill them.
542///
543/// ## TCL Liens
544///
545/// * In Standard TCL, `foreach` can loop over several lists at the same time.
546pub fn cmd_foreach<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
547    check_args(1, argv, 4, 4, "varList list body")?;
548
549    let var_list = &*argv[1].as_list()?;
550    let list = &*argv[2].as_list()?;
551    let body = &argv[3];
552
553    let mut i = 0;
554
555    while i < list.len() {
556        for var in var_list {
557            if i < list.len() {
558                interp.set_var(&var, list[i].clone())?;
559                i += 1;
560            } else {
561                interp.set_var(&var, Value::empty())?;
562            }
563        }
564
565        let result = interp.eval_value(body);
566
567        if let Err(exception) = result {
568            match exception.code() {
569                ResultCode::Break => break,
570                ResultCode::Continue => (),
571                _ => return Err(exception),
572            }
573        }
574    }
575
576    molt_ok!()
577}
578
579/// # global ?*varName* ...?
580///
581/// Appends any number of values to a variable's value, which need not
582/// initially exist.
583pub fn cmd_global<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
584    // Accepts any number of arguments
585
586    // FIRST, if we're at the global scope this is a no-op.
587    if interp.scope_level() > 0 {
588        for name in &argv[1..] {
589            // TODO: Should upvar take the name as a Value?
590            interp.upvar(0, name.as_str());
591        }
592    }
593    molt_ok!()
594}
595
596#[derive(Eq, PartialEq, Debug)]
597enum IfWants {
598    Expr,
599    ThenBody,
600    SkipThenClause,
601    ElseClause,
602    ElseBody,
603}
604
605/// # if *expr* ?then? *script* elseif *expr* ?then? *script* ... ?else? ?*script*?
606///
607/// Standard conditional.  Returns the value of the selected script (or
608/// "" if there is no else body and the none of the previous branches were selected).
609///
610/// # TCL Liens
611///
612/// * Because we don't yet have an expression parser, the *expr* arguments are evaluated as
613///   scripts that must return a boolean value.
614pub fn cmd_if<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
615    let mut argi = 1;
616    let mut wants = IfWants::Expr;
617
618    while argi < argv.len() {
619        match wants {
620            IfWants::Expr => {
621                wants = if interp.expr_bool(&argv[argi])? {
622                    IfWants::ThenBody
623                } else {
624                    IfWants::SkipThenClause
625                };
626            }
627            IfWants::ThenBody => {
628                if argv[argi].as_str() == "then" {
629                    argi += 1;
630                }
631
632                if argi < argv.len() {
633                    return interp.eval_value(&argv[argi]);
634                } else {
635                    break;
636                }
637            }
638            IfWants::SkipThenClause => {
639                if argv[argi].as_str() == "then" {
640                    argi += 1;
641                }
642
643                if argi < argv.len() {
644                    argi += 1;
645                    wants = IfWants::ElseClause;
646                }
647                continue;
648            }
649            IfWants::ElseClause => {
650                if argv[argi].as_str() == "elseif" {
651                    wants = IfWants::Expr;
652                } else {
653                    wants = IfWants::ElseBody;
654                    continue;
655                }
656            }
657            IfWants::ElseBody => {
658                if argv[argi].as_str() == "else" {
659                    argi += 1;
660
661                    // If "else" appears, then the else body is required.
662                    if argi == argv.len() {
663                        return molt_err!(
664                            "wrong # args: no script following after \"{}\" argument",
665                            argv[argi - 1]
666                        );
667                    }
668                }
669
670                if argi < argv.len() {
671                    return interp.eval_value(&argv[argi]);
672                } else {
673                    break;
674                }
675            }
676        }
677
678        argi += 1;
679    }
680
681    if argi < argv.len() {
682        return molt_err!(
683            "wrong # args: extra words after \"else\" clause in \"if\" command"
684        );
685    } else if wants == IfWants::Expr {
686        return molt_err!(
687            "wrong # args: no expression after \"{}\" argument",
688            argv[argi - 1]
689        );
690    } else if wants == IfWants::ThenBody || wants == IfWants::SkipThenClause {
691        return molt_err!(
692            "wrong # args: no script following after \"{}\" argument",
693            argv[argi - 1]
694        );
695    } else {
696        // Looking for ElseBody, but there doesn't need to be one.
697        molt_ok!() // temp
698    }
699}
700
701/// # incr *varName* ?*increment* ...?
702///
703/// Increments an integer variable by a value.
704pub fn cmd_incr<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
705    check_args(1, argv, 2, 3, "varName ?increment?")?;
706
707    let increment: MoltInt = if argv.len() == 3 { argv[2].as_int()? } else { 1 };
708
709    let new_value = increment
710        + interp
711            .var(&argv[1])
712            .and_then(|val| Ok(val.as_int()?))
713            .unwrap_or_else(|_| 0);
714
715    interp.set_var_return(&argv[1], new_value.into())
716}
717
718/// # info *subcommand* ?*arg*...?
719pub fn cmd_info<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
720    let f = _gen_subcommand_generic!(
721        1,
722        [
723            ("args", cmd_info_args),
724            ("body", cmd_info_body),
725            ("cmdtype", cmd_info_cmdtype),
726            ("commands", cmd_info_commands),
727            ("complete", cmd_info_complete),
728            ("default", cmd_info_default),
729            ("exists", cmd_info_exists),
730            ("globals", cmd_info_globals),
731            ("locals", cmd_info_locals),
732            ("procs", cmd_info_procs),
733            ("vars", cmd_info_vars),
734        ],
735    );
736    f(interp, argv)
737}
738
739/// # info args *procname*
740pub fn cmd_info_args<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
741    check_args(2, argv, 3, 3, "procname")?;
742    interp.proc_args(&argv[2].as_str())
743}
744
745/// # info body *procname*
746pub fn cmd_info_body<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
747    check_args(2, argv, 3, 3, "procname")?;
748    interp.proc_body(&argv[2].as_str())
749}
750
751/// # info cmdtype *command*
752pub fn cmd_info_cmdtype<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
753    check_args(2, argv, 3, 3, "command")?;
754    interp.command_type(&argv[2].as_str())
755}
756
757/// # info commands ?*pattern*?
758pub fn cmd_info_commands<Ctx>(interp: &mut Interp<Ctx>, _argv: &[Value]) -> MoltResult {
759    molt_ok!(Value::from(interp.command_names()))
760}
761
762/// # info default *procname* *arg* *varname*
763pub fn cmd_info_default<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
764    check_args(2, argv, 5, 5, "procname arg varname")?;
765
766    if let Some(val) = interp.proc_default(&argv[2].as_str(), &argv[3].as_str())? {
767        interp.set_var(&argv[4], val)?;
768        molt_ok!(1)
769    } else {
770        interp.set_var(&argv[4], Value::empty())?;
771        molt_ok!(0)
772    }
773}
774
775/// # info exists *varname*
776pub fn cmd_info_exists<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
777    check_args(2, argv, 3, 3, "varname")?;
778    Ok(interp.var_exists(&argv[2]).into())
779}
780
781/// # info complete *command*
782pub fn cmd_info_complete<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
783    check_args(2, argv, 3, 3, "command")?;
784
785    if interp.complete(argv[2].as_str()) {
786        molt_ok!(true)
787    } else {
788        molt_ok!(false)
789    }
790}
791
792/// # info globals
793/// TODO: Add glob matching as a feature, and provide optional pattern argument.
794pub fn cmd_info_globals<Ctx>(interp: &mut Interp<Ctx>, _argv: &[Value]) -> MoltResult {
795    molt_ok!(Value::from(interp.vars_in_global_scope()))
796}
797
798/// # info locals
799/// TODO: Add glob matching as a feature, and provide optional pattern argument.
800pub fn cmd_info_locals<Ctx>(interp: &mut Interp<Ctx>, _argv: &[Value]) -> MoltResult {
801    molt_ok!(Value::from(interp.vars_in_local_scope()))
802}
803
804/// # info procs ?*pattern*?
805pub fn cmd_info_procs<Ctx>(interp: &mut Interp<Ctx>, _argv: &[Value]) -> MoltResult {
806    molt_ok!(Value::from(interp.proc_names()))
807}
808
809/// # info vars
810/// TODO: Add glob matching as a feature, and provide optional pattern argument.
811pub fn cmd_info_vars<Ctx>(interp: &mut Interp<Ctx>, _argv: &[Value]) -> MoltResult {
812    molt_ok!(Value::from(interp.vars_in_scope()))
813}
814
815/// # join *list* ?*joinString*?
816///
817/// Joins the elements of a list with a string.  The join string defaults to " ".
818pub fn cmd_join<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
819    check_args(1, argv, 2, 3, "list ?joinString?")?;
820
821    let list = &argv[1].as_list()?;
822
823    let join_string = if argv.len() == 3 { argv[2].to_string() } else { " ".to_string() };
824
825    // TODO: Need to implement a standard join() method for MoltLists.
826    let list: Vec<String> = list.iter().map(|v| v.to_string()).collect();
827
828    molt_ok!(list.join(&join_string))
829}
830
831/// # lappend *varName* ?*value* ...?
832///
833/// Appends any number of values to a variable's list value, which need not
834/// initially exist.
835pub fn cmd_lappend<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
836    check_args(1, argv, 2, 0, "varName ?value ...?")?;
837
838    let var_result = interp.var(&argv[1]);
839
840    let mut list: MoltList = if var_result.is_ok() {
841        var_result.expect("got value").to_list()?
842    } else {
843        Vec::new()
844    };
845
846    let mut values = argv[2..].to_owned();
847    list.append(&mut values);
848    interp.set_var_return(&argv[1], Value::from(list))
849}
850
851/// # lindex *list* ?*index* ...?
852///
853/// Returns an element from the list, indexing into nested lists.
854pub fn cmd_lindex<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
855    check_args(1, argv, 2, 0, "list ?index ...?")?;
856
857    if argv.len() != 3 {
858        lindex_into(&argv[1], &argv[2..])
859    } else {
860        lindex_into(&argv[1], &*argv[2].as_list()?)
861    }
862}
863
864pub fn lindex_into(list: &Value, indices: &[Value]) -> MoltResult {
865    let mut value: Value = list.clone();
866
867    for index_val in indices {
868        let list = value.as_list()?;
869        let index = index_val.as_int()?;
870
871        value = if index < 0 || index as usize >= list.len() {
872            Value::empty()
873        } else {
874            list[index as usize].clone()
875        };
876    }
877
878    molt_ok!(value)
879}
880
881/// # list ?*arg*...?
882///
883/// Converts its arguments into a canonical list.
884pub fn cmd_list<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
885    // No arg check needed; can take any number.
886    molt_ok!(&argv[1..])
887}
888
889/// # llength *list*
890///
891/// Returns the length of the list.
892pub fn cmd_llength<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
893    check_args(1, argv, 2, 2, "list")?;
894
895    molt_ok!(argv[1].as_list()?.len() as MoltInt)
896}
897
898/// # pdump
899///
900/// Dumps profile data.  Developer use only.
901pub fn cmd_pdump<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
902    check_args(1, argv, 1, 1, "")?;
903
904    interp.profile_dump();
905
906    molt_ok!()
907}
908
909/// # pclear
910///
911/// Clears profile data.  Developer use only.
912pub fn cmd_pclear<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
913    check_args(1, argv, 1, 1, "")?;
914
915    interp.profile_clear();
916
917    molt_ok!()
918}
919
920/// # proc *name* *args* *body*
921///
922/// Defines a procedure.
923pub fn cmd_proc<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
924    check_args(1, argv, 4, 4, "name args body")?;
925
926    // FIRST, get the arguments
927    let name = argv[1].as_str();
928    let args = &*argv[2].as_list()?;
929
930    // NEXT, validate the argument specs
931    for arg in args {
932        let vec = arg.as_list()?;
933
934        if vec.is_empty() {
935            return molt_err!("argument with no name");
936        } else if vec.len() > 2 {
937            return molt_err!("too many fields in argument specifier \"{}\"", arg);
938        }
939    }
940
941    // NEXT, add the command.
942    interp.add_proc(name, args, &argv[3]);
943
944    molt_ok!()
945}
946
947/// # puts *string*
948///
949/// Outputs the string to stdout.
950///
951/// ## TCL Liens
952///
953/// * Does not support `-nonewline`
954/// * Does not support `channelId`
955pub fn cmd_puts<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
956    check_args(1, argv, 2, 2, "string")?;
957    cfg_if::cfg_if! {
958      if #[cfg(feature = "std_buff")] {
959        interp.std_buff.push(Ok(argv[1].clone()));
960      } else {
961        println!("{}", argv[1]);
962      }
963    }
964    molt_ok!()
965}
966
967// /// # rename *oldName* *newName*
968// ///
969// /// Renames the command called *oldName* to have the *newName*.  If the
970// /// *newName* is "", the command is destroyed.
971pub fn cmd_rename<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
972    check_args(1, argv, 3, 3, "oldName newName")?;
973
974    // FIRST, get the arguments
975    let old_name = argv[1].as_str();
976    let new_name = argv[2].as_str();
977
978    if !interp.has_proc(old_name) {
979        return molt_err!("can't rename \"{}\": command doesn't exist", old_name);
980    }
981
982    // NEXT, rename or remove the command.
983    if new_name.is_empty() {
984        interp.remove_proc(old_name);
985    } else {
986        interp.rename_proc(old_name, new_name);
987    }
988
989    molt_ok!()
990}
991
992/// # return ?-code code? ?-level level? ?value?
993///
994/// Returns from a proc with the given *value*, which defaults to the empty result.
995/// See the documentation for **return** in The Molt Book for the option semantics.
996///
997/// ## TCL Liens
998///
999/// * Doesn't support all of TCL's fancy return machinery. Someday it will.
1000pub fn cmd_return<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
1001    check_args(1, argv, 1, 0, "?options...? ?value?")?;
1002
1003    // FIRST, set the defaults
1004    let mut code = ResultCode::Okay;
1005    let mut level: MoltInt = 1;
1006    let mut error_code: Option<Value> = None;
1007    let mut error_info: Option<Value> = None;
1008
1009    // NEXT, with no arguments just return.
1010    if argv.len() == 1 {
1011        return Err(Exception::molt_return_ext(Value::empty(), level as usize, code));
1012    }
1013
1014    // NEXT, get the return value: the last argument, if there's an odd number of arguments
1015    // after the command name.
1016    let return_value: Value;
1017
1018    let opt_args: &[Value] = if argv.len() % 2 == 0 {
1019        // odd number of args following the command name
1020        return_value = argv[argv.len() - 1].clone();
1021        &argv[1..argv.len() - 1]
1022    } else {
1023        // even number of args following the command name
1024        return_value = Value::empty();
1025        &argv[1..argv.len()]
1026    };
1027
1028    // NEXT, Get any options
1029    let mut queue = opt_args.iter();
1030
1031    while let Some(opt) = queue.next() {
1032        // We built the queue to have an even number of arguments, and every option requires
1033        // a value; so there can't be a missing option value.
1034        let val = queue
1035            .next()
1036            .expect("missing option value: coding error in cmd_return");
1037
1038        match opt.as_str() {
1039            "-code" => {
1040                code = ResultCode::from_value(val)?;
1041            }
1042            "-errorcode" => {
1043                error_code = Some(val.clone());
1044            }
1045            "-errorinfo" => {
1046                error_info = Some(val.clone());
1047            }
1048            "-level" => {
1049                // TODO: return better error:
1050                // bad -level value: expected non-negative integer but got "{}"
1051                level = val.as_int()?;
1052            }
1053            // TODO: In standard TCL there are no invalid options; all options are retained.
1054            _ => return molt_err!("invalid return option: \"{}\"", opt),
1055        }
1056    }
1057
1058    // NEXT, return the result: normally a Return exception, but could be "Ok".
1059    if code == ResultCode::Error {
1060        Err(Exception::molt_return_err(
1061            return_value,
1062            level as usize,
1063            error_code,
1064            error_info,
1065        ))
1066    } else if level == 0 && code == ResultCode::Okay {
1067        // Not an exception!j
1068        Ok(return_value)
1069    } else {
1070        Err(Exception::molt_return_ext(return_value, level as usize, code))
1071    }
1072}
1073
1074/// # set *varName* ?*newValue*?
1075///
1076/// Sets variable *varName* to *newValue*, returning the value.
1077/// If *newValue* is omitted, returns the variable's current value,
1078/// returning an error if the variable is unknown.
1079pub fn cmd_set<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
1080    check_args(1, argv, 2, 3, "varName ?newValue?")?;
1081
1082    if argv.len() == 3 {
1083        interp.set_var_return(&argv[1], argv[2].clone())
1084    } else {
1085        molt_ok!(interp.var(&argv[1])?)
1086    }
1087}
1088
1089/// # source *filename*
1090///
1091/// Sources the file, returning the result.
1092pub fn cmd_source<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
1093    check_args(1, argv, 2, 2, "filename")?;
1094
1095    let filename = argv[1].as_str();
1096
1097    match fs::read_to_string(filename) {
1098        Ok(script) => interp.eval(&script),
1099        Err(e) => molt_err!("couldn't read file \"{}\": {}", filename, e),
1100    }
1101}
1102
1103/// # string *subcommand* ?*arg*...?
1104///
1105/// https://www.tcl.tk/man/tcl8.6/TclCmd/string.htm
1106pub fn cmd_string<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
1107    // cfg_if::cfg_if! {
1108    //     if #[cfg(feature = "native_subcmd_help")] {
1109    //         let f = gen_subcommand!(
1110    //             Ctx,
1111    //             1,
1112    //             [
1113    //                 ("cat","       ", cmd_string_cat,"string cat ?string1? ?string2...?"),
1114    //                 ("compare","   ", cmd_string_compare,"string compare ?-nocase? ?-length length? string1 string2"),
1115    //                 ("equal","     ", cmd_string_equal,"string equal ?-nocase? ?-length length? string1 string2"),
1116    //                 ("first","     ", cmd_string_first,"string first needleString haystackString ?startIndex?"),
1117    //                 ("index","     ", cmd_todo,"string index string charIndex"),
1118    //                 ("is","        ", cmd_todo,"[TODO] string is class ?-strict? ?-failindex varname? string"),
1119    //                 ("last","      ", cmd_string_last,"string last needleString haystackString ?lastIndex?"),
1120    //                 ("length","    ", cmd_string_length,"string length string"),
1121    //                 ("map","       ", cmd_string_map,"string map ?-nocase? mapping string"),
1122    //                 ("match","     ", cmd_todo,"[TODO] string match ?-nocase? pattern string"),
1123    //                 ("range","     ", cmd_string_range,"string range string first last"),
1124    //                 ("repeat","    ", cmd_todo,"[TODO] string repeat string count"),
1125    //                 ("replace","   ", cmd_todo,"[TODO] string replace string first last ?newstring?"),
1126    //                 ("reverse","   ", cmd_todo,"[TODO] string reverse string"),
1127    //                 ("tolower","   ", cmd_string_tolower,"string tolower string ?first? ?last?"),
1128    //                 ("totitle","   ", cmd_todo,"[TODO] string totitle string ?first? ?last?"),
1129    //                 ("toupper","   ", cmd_string_toupper,"string toupper string ?first? ?last?"),
1130    //                 ("trim","      ", cmd_string_trim,"string trim string ?chars?"),
1131    //                 ("trimleft","  ", cmd_string_trim,"string trimleft string ?chars?"),
1132    //                 ("trimright"," ", cmd_string_trim,"string trimright string ?chars?"),
1133    //                 ("bytelength","", cmd_todo,"[TODO] string bytelength string"),
1134    //                 ("wordend","   ", cmd_todo,"[TODO] string wordend string charIndex"),
1135    //                 ("wordstart"," ", cmd_todo,"[TODO] string wordstart string charIndex"),
1136    //             ],
1137    //         );
1138
1139    //     }else{
1140    let f = _gen_subcommand_generic!(
1141        1,
1142        [
1143            ("cat", cmd_string_cat),
1144            ("compare", cmd_string_compare),
1145            ("equal", cmd_string_equal),
1146            ("first", cmd_string_first),
1147            // ("index", cmd_todo),
1148            ("last", cmd_string_last),
1149            ("length", cmd_string_length),
1150            ("map", cmd_string_map),
1151            ("range", cmd_string_range),
1152            // ("replace", cmd_todo),
1153            // ("repeat", cmd_todo),
1154            // ("reverse", cmd_todo),
1155            ("tolower", cmd_string_tolower),
1156            ("toupper", cmd_string_toupper),
1157            ("trim", cmd_string_trim),
1158            ("trimleft", cmd_string_trim),
1159            ("trimright", cmd_string_trim),
1160        ],
1161    );
1162    //     }
1163    // }
1164    f(interp, argv)
1165}
1166
1167/// TODO cmds
1168pub fn cmd_todo<Ctx>(_interp: &mut Interp<Ctx>, _argv: &[Value]) -> MoltResult {
1169    molt_err!("TODO")
1170}
1171
1172/// string cat ?*arg* ...?
1173pub fn cmd_string_cat<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
1174    let mut buff = String::new();
1175
1176    for arg in &argv[2..] {
1177        buff.push_str(arg.as_str());
1178    }
1179
1180    molt_ok!(buff)
1181}
1182
1183/// string compare ?-nocase? ?-length length? string1 string2
1184pub fn cmd_string_compare<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
1185    check_args(2, argv, 4, 7, "?-nocase? ?-length length? string1 string2")?;
1186
1187    // FIRST, set the defaults.
1188    let arglen = argv.len();
1189    let mut nocase = false;
1190    let mut length: Option<MoltInt> = None;
1191
1192    // NEXT, get options
1193    let opt_args = &argv[2..arglen - 2];
1194    let mut queue = opt_args.iter();
1195
1196    while let Some(opt) = queue.next() {
1197        match opt.as_str() {
1198            "-nocase" => nocase = true,
1199            "-length" => {
1200                if let Some(val) = queue.next() {
1201                    length = Some(val.as_int()?);
1202                } else {
1203                    return molt_err!("wrong # args: should be \"string compare ?-nocase? ?-length length? string1 string2\"");
1204                }
1205            }
1206            _ => return molt_err!("bad option \"{}\": must be -nocase or -length", opt),
1207        }
1208    }
1209
1210    if nocase {
1211        let val1 = &argv[arglen - 2];
1212        let val2 = &argv[arglen - 1];
1213
1214        // TODO: *Not* the best way to do this; consider using the unicase crate.
1215        let val1 = Value::from(val1.as_str().to_lowercase());
1216        let val2 = Value::from(val2.as_str().to_lowercase());
1217
1218        molt_ok!(util::compare_len(val1.as_str(), val2.as_str(), length)?)
1219    } else {
1220        molt_ok!(util::compare_len(
1221            argv[arglen - 2].as_str(),
1222            argv[arglen - 1].as_str(),
1223            length
1224        )?)
1225    }
1226}
1227
1228/// string equal ?-nocase? ?-length length? string1 string2
1229pub fn cmd_string_equal<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
1230    check_args(2, argv, 4, 7, "?-nocase? ?-length length? string1 string2")?;
1231
1232    // FIRST, set the defaults.
1233    let arglen = argv.len();
1234    let mut nocase = false;
1235    let mut length: Option<MoltInt> = None;
1236
1237    // NEXT, get options
1238    let opt_args = &argv[2..arglen - 2];
1239    let mut queue = opt_args.iter();
1240
1241    while let Some(opt) = queue.next() {
1242        match opt.as_str() {
1243            "-nocase" => nocase = true,
1244            "-length" => {
1245                if let Some(val) = queue.next() {
1246                    length = Some(val.as_int()?);
1247                } else {
1248                    return molt_err!("wrong # args: should be \"string equal ?-nocase? ?-length length? string1 string2\"");
1249                }
1250            }
1251            _ => return molt_err!("bad option \"{}\": must be -nocase or -length", opt),
1252        }
1253    }
1254
1255    if nocase {
1256        let val1 = &argv[arglen - 2];
1257        let val2 = &argv[arglen - 1];
1258
1259        // TODO: *Not* the best way to do this; consider using the unicase crate.
1260        let val1 = Value::from(val1.as_str().to_lowercase());
1261        let val2 = Value::from(val2.as_str().to_lowercase());
1262
1263        let flag = util::compare_len(val1.as_str(), val2.as_str(), length)? == 0;
1264        molt_ok!(flag)
1265    } else {
1266        let flag = util::compare_len(
1267            argv[arglen - 2].as_str(),
1268            argv[arglen - 1].as_str(),
1269            length,
1270        )? == 0;
1271        molt_ok!(flag)
1272    }
1273}
1274
1275/// string first *needleString* *haystackString* ?*startIndex*?
1276pub fn cmd_string_first<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
1277    check_args(2, argv, 4, 5, "needleString haystackString ?startIndex?")?;
1278
1279    let needle = argv[2].as_str();
1280    let haystack = argv[3].as_str();
1281
1282    let start_char: usize = if argv.len() == 5 {
1283        let arg = argv[4].as_int()?;
1284
1285        if arg < 0 {
1286            0
1287        } else {
1288            arg as usize
1289        }
1290    } else {
1291        0
1292    };
1293
1294    let pos_byte: Option<usize> = haystack
1295        .char_indices()
1296        .nth(start_char)
1297        .and_then(|(start_byte, _)| haystack[start_byte..].find(needle));
1298
1299    let pos_char: MoltInt = match pos_byte {
1300        None => -1,
1301        Some(b) => {
1302            haystack[b..].char_indices().take_while(|(i, _)| *i < b).count() as MoltInt
1303                + start_char as MoltInt
1304        }
1305    };
1306
1307    molt_ok!(pos_char)
1308}
1309
1310/// string last *needleString* *haystackString* ?*lastIndex*?
1311pub fn cmd_string_last<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
1312    check_args(2, argv, 4, 5, "needleString haystackString ?lastIndex?")?;
1313
1314    let needle = argv[2].as_str();
1315    let haystack = argv[3].as_str();
1316
1317    let count = haystack.chars().count();
1318
1319    let last: Option<usize> = if argv.len() == 5 {
1320        let arg = argv[4].as_int()?;
1321
1322        if arg < 0 {
1323            return molt_ok!(-1);
1324        }
1325
1326        if arg as usize >= count {
1327            None
1328        } else {
1329            Some(arg as usize)
1330        }
1331    } else {
1332        None
1333    };
1334
1335    let slice = match last {
1336        None => haystack,
1337        Some(n) => match haystack.char_indices().nth(n + 1) {
1338            None => haystack,
1339            Some((byte, _)) => &haystack[..byte],
1340        },
1341    };
1342
1343    let pos_byte = slice.rfind(needle);
1344
1345    let pos_char: MoltInt = match pos_byte {
1346        None => -1,
1347        Some(b) => haystack.char_indices().take_while(|(i, _)| *i < b).count() as MoltInt,
1348    };
1349
1350    molt_ok!(pos_char)
1351}
1352
1353/// string length *string*
1354pub fn cmd_string_length<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
1355    check_args(2, argv, 3, 3, "string")?;
1356
1357    let len: MoltInt = argv[2].as_str().chars().count() as MoltInt;
1358    molt_ok!(len)
1359}
1360
1361/// string map ?-nocase? *charMap* *string*
1362pub fn cmd_string_map<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
1363    check_args(2, argv, 4, 5, "?-nocase? charMap string")?;
1364
1365    let mut nocase = false;
1366
1367    if argv.len() == 5 {
1368        let opt = argv[2].as_str();
1369
1370        if opt == "-nocase" {
1371            nocase = true;
1372        } else {
1373            return molt_err!("bad option \"{}\": must be -nocase", opt);
1374        }
1375    }
1376
1377    let char_map = argv[argv.len() - 2].as_dict()?;
1378    let s = argv[argv.len() - 1].as_str();
1379
1380    let filtered_keys = char_map
1381        .iter()
1382        .map(|(k, v)| {
1383            let new_k =
1384                if nocase { Value::from(k.as_str().to_lowercase()) } else { k.clone() };
1385
1386            let count = new_k.as_str().chars().count();
1387
1388            (new_k, count, v.clone())
1389        })
1390        .filter(|(_, count, _)| *count > 0)
1391        .collect::<Vec<_>>();
1392
1393    let string_lower: Option<String> = if nocase { Some(s.to_lowercase()) } else { None };
1394
1395    let mut result = String::new();
1396    let mut skip = 0;
1397
1398    for (i, c) in s.char_indices() {
1399        if skip > 0 {
1400            skip -= 1;
1401            continue;
1402        }
1403
1404        let mut matched = false;
1405
1406        for (from, from_char_count, to) in &filtered_keys {
1407            let haystack: &str = match &string_lower {
1408                Some(x) => &x[i..],
1409                None => &s[i..],
1410            };
1411
1412            if haystack.starts_with(&from.as_str()) {
1413                matched = true;
1414
1415                result.push_str(to.as_str());
1416                skip = from_char_count - 1;
1417
1418                break;
1419            }
1420        }
1421
1422        if !matched {
1423            result.push(c);
1424        }
1425    }
1426
1427    molt_ok!(result)
1428}
1429
1430/// string range *string* *first* *last*
1431pub fn cmd_string_range<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
1432    check_args(2, argv, 5, 5, "string first last")?;
1433
1434    let s = argv[2].as_str();
1435    let first = argv[3].as_int()?;
1436    let last = argv[4].as_int()?;
1437
1438    if last < 0 {
1439        return molt_ok!("");
1440    }
1441
1442    let clamp = { |i: MoltInt| if i < 0 { 0 } else { i } };
1443
1444    let substr = s
1445        .chars()
1446        .skip(clamp(first) as usize)
1447        .take((clamp(last) - clamp(first) + 1) as usize)
1448        .collect::<String>();
1449
1450    molt_ok!(substr)
1451}
1452
1453/// string tolower *string*
1454pub fn cmd_string_tolower<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
1455    check_args(2, argv, 3, 3, "string")?;
1456
1457    let lower = argv[2].as_str().to_lowercase();
1458    molt_ok!(lower)
1459}
1460
1461/// string toupper *string*
1462pub fn cmd_string_toupper<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
1463    check_args(2, argv, 3, 3, "string")?;
1464
1465    let upper = argv[2].as_str().to_uppercase();
1466    molt_ok!(upper)
1467}
1468
1469/// string (trim|trimleft|trimright) *string*
1470pub fn cmd_string_trim<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
1471    check_args(2, argv, 3, 3, "string")?;
1472
1473    let s = argv[2].as_str();
1474    let trimmed = match argv[1].as_str() {
1475        "trimleft" => s.trim_start(),
1476        "trimright" => s.trim_end(),
1477        _ => s.trim(),
1478    };
1479
1480    molt_ok!(trimmed)
1481}
1482
1483/// throw *type* *message*
1484///
1485/// Throws an error with the error code and message.
1486pub fn cmd_throw<Ctx>(_interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
1487    check_args(1, argv, 3, 3, "type message")?;
1488
1489    Err(Exception::molt_err2(argv[1].clone(), argv[2].clone()))
1490}
1491
1492/// # time *command* ?*count*?
1493///
1494/// Executes the command the given number of times, and returns the average
1495/// number of microseconds per iteration.  The *count* defaults to 1.
1496pub fn cmd_time<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
1497    check_args(1, argv, 2, 3, "command ?count?")?;
1498
1499    let command = &argv[1];
1500
1501    let count = if argv.len() == 3 { argv[2].as_int()? } else { 1 };
1502
1503    let start = Instant::now();
1504
1505    for _i in 0..count {
1506        let result = interp.eval_value(command);
1507        if result.is_err() {
1508            return result;
1509        }
1510    }
1511
1512    let span = start.elapsed();
1513
1514    let avg = if count > 0 { span.as_nanos() / (count as u128) } else { 0 } as MoltInt;
1515
1516    molt_ok!("{} nanoseconds per iteration", avg)
1517}
1518
1519/// # unset ?-nocomplain? *varName*
1520///
1521/// Removes the variable from the interpreter.  This is a no op if
1522/// there is no such variable.  The -nocomplain option is accepted for
1523/// compatible with standard TCL, but is never required.
1524pub fn cmd_unset<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
1525    check_args(1, argv, 1, 0, "?-nocomplain? ?--? ?name name name...?")?;
1526
1527    let mut options_ok = true;
1528
1529    for arg in argv {
1530        let var = arg.as_str();
1531
1532        if options_ok {
1533            if var == "--" {
1534                options_ok = false;
1535                continue;
1536            } else if var == "-nocomplain" {
1537                continue;
1538            }
1539        }
1540
1541        interp.unset_var(arg);
1542    }
1543
1544    molt_ok!()
1545}
1546
1547/// # while *test* *command*
1548///
1549/// A standard "while" loop.  *test* is a boolean expression; *command* is a script to
1550/// execute so long as the expression is true.
1551pub fn cmd_while<Ctx>(interp: &mut Interp<Ctx>, argv: &[Value]) -> MoltResult {
1552    check_args(1, argv, 3, 3, "test command")?;
1553
1554    while interp.expr_bool(&argv[1])? {
1555        let result = interp.eval_value(&argv[2]);
1556
1557        if let Err(exception) = result {
1558            match exception.code() {
1559                ResultCode::Break => break,
1560                ResultCode::Continue => (),
1561                _ => return Err(exception),
1562            }
1563        }
1564    }
1565
1566    molt_ok!()
1567}