Skip to main content

molt/
commands.rs

1//! # Standard Molt Command Definitions
2//!
3//! This module defines the standard Molt commands.
4
5use crate::dict::dict_new;
6use crate::dict::dict_path_insert;
7use crate::dict::dict_path_remove;
8use crate::dict::list_to_dict;
9use crate::interp::Interp;
10use crate::types::*;
11use crate::util;
12use crate::*;
13use std::fs;
14use std::time::Instant;
15
16/// # append *varName* ?*value* ...?
17///
18/// Appends one or more strings to a variable.
19/// See molt-book for full semantics.
20pub fn cmd_append(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
21    check_args(1, argv, 2, 0, "varName ?value value ...?")?;
22
23    // FIRST, get the value of the variable.  If the variable is undefined,
24    // start with the empty string.
25    let mut new_string: String = interp
26        .var(&argv[1])
27        .and_then(|val| Ok(val.to_string()))
28        .unwrap_or_else(|_| String::new());
29
30    // NEXT, append the remaining values to the string.
31    for item in &argv[2..] {
32        new_string.push_str(item.as_str());
33    }
34
35    // NEXT, save and return the new value.
36    interp.set_var_return(&argv[1], new_string.into())
37}
38
39/// # array *subcommand* ?*arg*...?
40pub fn cmd_array(interp: &mut Interp, context_id: ContextID, argv: &[Value]) -> MoltResult {
41    interp.call_subcommand(context_id, argv, 1, &ARRAY_SUBCOMMANDS)
42}
43
44const ARRAY_SUBCOMMANDS: [Subcommand; 6] = [
45    Subcommand("exists", cmd_array_exists),
46    Subcommand("get", cmd_array_get),
47    Subcommand("names", cmd_array_names),
48    Subcommand("set", cmd_array_set),
49    Subcommand("size", cmd_array_size),
50    Subcommand("unset", cmd_array_unset),
51];
52
53/// # array exists arrayName
54pub fn cmd_array_exists(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
55    check_args(2, argv, 3, 3, "arrayName")?;
56    molt_ok!(Value::from(interp.array_exists(argv[2].as_str())))
57}
58
59/// # array names arrayName
60/// TODO: Add glob matching as a feature, and support standard TCL options.
61pub fn cmd_array_names(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
62    check_args(2, argv, 3, 3, "arrayName")?;
63    molt_ok!(Value::from(interp.array_names(argv[2].as_str())))
64}
65
66/// # array get arrayname
67/// TODO: Add glob matching as a feature, and support standard TCL options.
68pub fn cmd_array_get(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
69    check_args(2, argv, 3, 3, "arrayName")?;
70    molt_ok!(Value::from(interp.array_get(argv[2].as_str())))
71}
72
73/// # array set arrayName list
74pub fn cmd_array_set(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
75    check_args(2, argv, 4, 4, "arrayName list")?;
76
77    // This odd little dance provides the same semantics as Standard TCL.  If the
78    // given var_name has an index, the array is created (if it didn't exist)
79    // but no data is added to it, and the command returns an error.
80    let var_name = argv[2].as_var_name();
81
82    if var_name.index().is_none() {
83        interp.array_set(var_name.name(), &*argv[3].as_list()?)
84    } else {
85        // This line will create the array if it doesn't exist, and throw an error if the
86        // named variable exists but isn't an array.  This is a little wacky, but it's
87        // what TCL 8.6 does.
88        interp.array_set(var_name.name(), &*Value::empty().as_list()?)?;
89
90        // And this line throws an error because the full name the caller specified is an
91        // element, not the array itself.
92        molt_err!("can't set \"{}\": variable isn't array", &argv[2])
93    }
94}
95
96/// # array size arrayName
97pub fn cmd_array_size(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
98    check_args(2, argv, 3, 3, "arrayName")?;
99    molt_ok!(Value::from(interp.array_size(argv[2].as_str()) as MoltInt))
100}
101
102/// # array unset arrayName ?*index*?
103pub fn cmd_array_unset(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
104    check_args(2, argv, 3, 4, "arrayName ?index?")?;
105
106    if argv.len() == 3 {
107        interp.array_unset(argv[2].as_str());
108    } else {
109        interp.unset_element(argv[2].as_str(), argv[3].as_str());
110    }
111    molt_ok!()
112}
113
114/// assert_eq received, expected
115///
116/// Asserts that two values have identical string representations.
117/// See molt-book for full semantics.
118pub fn cmd_assert_eq(_interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
119    check_args(1, argv, 3, 3, "received expected")?;
120
121    if argv[1] == argv[2] {
122        molt_ok!()
123    } else {
124        molt_err!(
125            "assertion failed: received \"{}\", expected \"{}\".",
126            argv[1],
127            argv[2]
128        )
129    }
130}
131
132/// # break
133///
134/// Breaks a loops.
135/// See molt-book for full semantics.
136pub fn cmd_break(_interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
137    check_args(1, argv, 1, 1, "")?;
138
139    Err(Exception::molt_break())
140}
141
142/// catch script ?resultVarName? ?optionsVarName?
143///
144/// Executes a script, returning the result code.  If the resultVarName is given, the result
145/// of executing the script is returned in it.  The result code is returned as an integer,
146/// 0=Ok, 1=Error, 2=Return, 3=Break, 4=Continue.
147pub fn cmd_catch(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
148    check_args(1, argv, 2, 4, "script ?resultVarName? ?optionsVarName?")?;
149
150    // If the script called `return x`, should get Return, -level 1, -code Okay here
151    let result = interp.eval_value(&argv[1]);
152
153    let (code, value) = match &result {
154        Ok(val) => (0, val.clone()),
155        Err(exception) => match exception.code() {
156            ResultCode::Okay => unreachable!(), // Should not be reachable here.
157            ResultCode::Error => (1, exception.value()),
158            ResultCode::Return => (2, exception.value()),
159            ResultCode::Break => (3, exception.value()),
160            ResultCode::Continue => (4, exception.value()),
161            ResultCode::Other(_) => unimplemented!(), // TODO: Not in use yet
162        },
163    };
164
165    if argv.len() >= 3 {
166        interp.set_var(&argv[2], value)?;
167    }
168
169    if argv.len() == 4 {
170        interp.set_var(&argv[3], interp.return_options(&result))?;
171    }
172
173    Ok(Value::from(code))
174}
175
176/// # continue
177///
178/// Continues with the next iteration of the inmost loop.
179pub fn cmd_continue(_interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
180    check_args(1, argv, 1, 1, "")?;
181
182    Err(Exception::molt_continue())
183}
184
185/// # dict *subcommand* ?*arg*...?
186pub fn cmd_dict(interp: &mut Interp, context_id: ContextID, argv: &[Value]) -> MoltResult {
187    interp.call_subcommand(context_id, argv, 1, &DICT_SUBCOMMANDS)
188}
189
190const DICT_SUBCOMMANDS: [Subcommand; 9] = [
191    Subcommand("create", cmd_dict_new),
192    Subcommand("exists", cmd_dict_exists),
193    Subcommand("get", cmd_dict_get),
194    Subcommand("keys", cmd_dict_keys),
195    Subcommand("remove", cmd_dict_remove),
196    Subcommand("set", cmd_dict_set),
197    Subcommand("size", cmd_dict_size),
198    Subcommand("unset", cmd_dict_unset),
199    Subcommand("values", cmd_dict_values),
200];
201
202/// # dict create ?key value ...?
203fn cmd_dict_new(_: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
204    // FIRST, we need an even number of arguments.
205    if argv.len() % 2 != 0 {
206        return molt_err!(
207            "wrong # args: should be \"{} {}\"",
208            Value::from(&argv[0..2]).to_string(),
209            "?key value?"
210        );
211    }
212
213    // NEXT, return the value.
214    if argv.len() > 2 {
215        molt_ok!(Value::from(list_to_dict(&argv[2..])))
216    } else {
217        molt_ok!(Value::from(dict_new()))
218    }
219}
220
221/// # dict exists *dictionary* key ?*key* ...?
222fn cmd_dict_exists(_: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
223    check_args(2, argv, 4, 0, "dictionary key ?key ...?")?;
224
225    let mut value: Value = argv[2].clone();
226    let indices = &argv[3..];
227
228    for index in indices {
229        if let Ok(dict) = value.as_dict() {
230            if let Some(val) = dict.get(index) {
231                value = val.clone();
232            } else {
233                return molt_ok!(false);
234            }
235        } else {
236            return molt_ok!(false);
237        }
238    }
239
240    molt_ok!(true)
241}
242
243/// # dict get *dictionary* ?*key* ...?
244fn cmd_dict_get(_: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
245    check_args(2, argv, 3, 0, "dictionary ?key ...?")?;
246
247    let mut value: Value = argv[2].clone();
248    let indices = &argv[3..];
249
250    for index in indices {
251        let dict = value.as_dict()?;
252
253        if let Some(val) = dict.get(index) {
254            value = val.clone();
255        } else {
256            return molt_err!("key \"{}\" not known in dictionary", index);
257        }
258    }
259
260    molt_ok!(value)
261}
262
263/// # dict keys *dictionary*
264/// TODO: Add filtering when we have glob matching.
265fn cmd_dict_keys(_: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
266    check_args(2, argv, 3, 3, "dictionary")?;
267
268    let dict = argv[2].as_dict()?;
269    let keys: MoltList = dict.keys().cloned().collect();
270    molt_ok!(keys)
271}
272
273/// # dict remove *dictionary* ?*key* ...?
274fn cmd_dict_remove(_: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
275    check_args(2, argv, 3, 0, "dictionary ?key ...?")?;
276
277    // FIRST, get and clone the dictionary, so we can modify it.
278    let mut dict = (&*argv[2].as_dict()?).clone();
279
280    // NEXT, remove the given keys.
281    for key in &argv[3..] {
282        // shift_remove preserves the order of the keys.
283        dict.shift_remove(key);
284    }
285
286    // NEXT, return it as a new Value.
287    molt_ok!(dict)
288}
289
290/// # dict set *dictVarName* *key* ?*key* ...? *value*
291fn cmd_dict_set(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
292    check_args(2, argv, 5, 0, "dictVarName key ?key ...? value")?;
293
294    let value = &argv[argv.len() - 1];
295    let keys = &argv[3..(argv.len() - 1)];
296
297    if let Ok(old_dict_val) = interp.var(&argv[2]) {
298        interp.set_var_return(&argv[2], dict_path_insert(&old_dict_val, keys, value)?)
299    } else {
300        let new_val = Value::from(dict_new());
301        interp.set_var_return(&argv[2], dict_path_insert(&new_val, keys, value)?)
302    }
303}
304
305/// # dict size *dictionary*
306fn cmd_dict_size(_: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
307    check_args(2, argv, 3, 3, "dictionary")?;
308
309    let dict = argv[2].as_dict()?;
310    molt_ok!(dict.len() as MoltInt)
311}
312
313/// # dict unset *dictVarName* *key* ?*key* ...?
314fn cmd_dict_unset(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
315    check_args(2, argv, 4, 0, "dictVarName key ?key ...?")?;
316
317    let keys = &argv[3..];
318
319    if let Ok(old_dict_val) = interp.var(&argv[2]) {
320        interp.set_var_return(&argv[2], dict_path_remove(&old_dict_val, keys)?)
321    } else {
322        let new_val = Value::from(dict_new());
323        interp.set_var_return(&argv[2], dict_path_remove(&new_val, keys)?)
324    }
325}
326
327/// # dict values *dictionary*
328/// TODO: Add filtering when we have glob matching.
329fn cmd_dict_values(_: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
330    check_args(2, argv, 3, 3, "dictionary")?;
331
332    let dict = argv[2].as_dict()?;
333    let values: MoltList = dict.values().cloned().collect();
334    molt_ok!(values)
335}
336
337/// error *message*
338///
339/// Returns an error with the given message.
340///
341/// ## TCL Liens
342///
343/// * In Standard TCL, `error` can optionally set the stack trace and an error code.
344pub fn cmd_error(_interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
345    check_args(1, argv, 2, 2, "message")?;
346
347    molt_err!(argv[1].clone())
348}
349
350/// # exit ?*returnCode*?
351///
352/// Terminates the application by calling `std::process::exit()`.
353/// If given, _returnCode_ must be an integer return code; if absent, it
354/// defaults to 0.
355pub fn cmd_exit(_interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
356    check_args(1, argv, 1, 2, "?returnCode?")?;
357
358    let return_code: MoltInt = if argv.len() == 1 {
359        0
360    } else {
361        argv[1].as_int()?
362    };
363
364    std::process::exit(return_code as i32)
365}
366
367/// # expr expr
368///
369/// Evaluates an expression and returns its result.
370///
371/// ## TCL Liens
372///
373/// See the Molt Book.
374
375pub fn cmd_expr(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
376    check_args(1, argv, 2, 2, "expr")?;
377
378    interp.expr(&argv[1])
379}
380
381/// # for *start* *test* *next* *command*
382///
383/// A standard "for" loop.  start, next, and command are scripts; test is an expression
384///
385pub fn cmd_for(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
386    check_args(1, argv, 5, 5, "start test next command")?;
387
388    let start = &argv[1];
389    let test = &argv[2];
390    let next = &argv[3];
391    let command = &argv[4];
392
393    // Start
394    interp.eval_value(start)?;
395
396    while interp.expr_bool(test)? {
397        let result = interp.eval_value(command);
398
399        if let Err(exception) = result {
400            match exception.code() {
401                ResultCode::Break => break,
402                ResultCode::Continue => (),
403                _ => return Err(exception),
404            }
405        }
406
407        // Execute next script.  Break is allowed, but continue is not.
408        let result = interp.eval_value(next);
409
410        if let Err(exception) = result {
411            match exception.code() {
412                ResultCode::Break => break,
413                ResultCode::Continue => {
414                    return molt_err!("invoked \"continue\" outside of a loop");
415                }
416                _ => return Err(exception),
417            }
418        }
419    }
420
421    molt_ok!()
422}
423
424/// # foreach *varList* *list* *body*
425///
426/// Loops over the items the list, assigning successive items to the variables in the
427/// *varList* and calling the *body* as a script once for each set of assignments.
428/// On the last iteration, the second and subsequents variables in the *varList* will
429/// be assigned the empty string if there are not enough list elements to fill them.
430///
431/// ## TCL Liens
432///
433/// * In Standard TCL, `foreach` can loop over several lists at the same time.
434pub fn cmd_foreach(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
435    check_args(1, argv, 4, 4, "varList list body")?;
436
437    let var_list = &*argv[1].as_list()?;
438    let list = &*argv[2].as_list()?;
439    let body = &argv[3];
440
441    let mut i = 0;
442
443    while i < list.len() {
444        for var in var_list {
445            if i < list.len() {
446                interp.set_var(&var, list[i].clone())?;
447                i += 1;
448            } else {
449                interp.set_var(&var, Value::empty())?;
450            }
451        }
452
453        let result = interp.eval_value(body);
454
455        if let Err(exception) = result {
456            match exception.code() {
457                ResultCode::Break => break,
458                ResultCode::Continue => (),
459                _ => return Err(exception),
460            }
461        }
462    }
463
464    molt_ok!()
465}
466
467/// # global ?*varName* ...?
468///
469/// Appends any number of values to a variable's value, which need not
470/// initially exist.
471pub fn cmd_global(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
472    // Accepts any number of arguments
473
474    // FIRST, if we're at the global scope this is a no-op.
475    if interp.scope_level() > 0 {
476        for name in &argv[1..] {
477            // TODO: Should upvar take the name as a Value?
478            interp.upvar(0, name.as_str());
479        }
480    }
481    molt_ok!()
482}
483
484#[derive(Eq, PartialEq, Debug)]
485enum IfWants {
486    Expr,
487    ThenBody,
488    SkipThenClause,
489    ElseClause,
490    ElseBody,
491}
492
493/// # if *expr* ?then? *script* elseif *expr* ?then? *script* ... ?else? ?*script*?
494///
495/// Standard conditional.  Returns the value of the selected script (or
496/// "" if there is no else body and the none of the previous branches were selected).
497///
498/// # TCL Liens
499///
500/// * Because we don't yet have an expression parser, the *expr* arguments are evaluated as
501///   scripts that must return a boolean value.
502pub fn cmd_if(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
503    let mut argi = 1;
504    let mut wants = IfWants::Expr;
505
506    while argi < argv.len() {
507        match wants {
508            IfWants::Expr => {
509                wants = if interp.expr_bool(&argv[argi])? {
510                    IfWants::ThenBody
511                } else {
512                    IfWants::SkipThenClause
513                };
514            }
515            IfWants::ThenBody => {
516                if argv[argi].as_str() == "then" {
517                    argi += 1;
518                }
519
520                if argi < argv.len() {
521                    return interp.eval_value(&argv[argi]);
522                } else {
523                    break;
524                }
525            }
526            IfWants::SkipThenClause => {
527                if argv[argi].as_str() == "then" {
528                    argi += 1;
529                }
530
531                if argi < argv.len() {
532                    argi += 1;
533                    wants = IfWants::ElseClause;
534                }
535                continue;
536            }
537            IfWants::ElseClause => {
538                if argv[argi].as_str() == "elseif" {
539                    wants = IfWants::Expr;
540                } else {
541                    wants = IfWants::ElseBody;
542                    continue;
543                }
544            }
545            IfWants::ElseBody => {
546                if argv[argi].as_str() == "else" {
547                    argi += 1;
548
549                    // If "else" appears, then the else body is required.
550                    if argi == argv.len() {
551                        return molt_err!(
552                            "wrong # args: no script following after \"{}\" argument",
553                            argv[argi - 1]
554                        );
555                    }
556                }
557
558                if argi < argv.len() {
559                    return interp.eval_value(&argv[argi]);
560                } else {
561                    break;
562                }
563            }
564        }
565
566        argi += 1;
567    }
568
569    if argi < argv.len() {
570        return molt_err!("wrong # args: extra words after \"else\" clause in \"if\" command");
571    } else if wants == IfWants::Expr {
572        return molt_err!(
573            "wrong # args: no expression after \"{}\" argument",
574            argv[argi - 1]
575        );
576    } else if wants == IfWants::ThenBody || wants == IfWants::SkipThenClause {
577        return molt_err!(
578            "wrong # args: no script following after \"{}\" argument",
579            argv[argi - 1]
580        );
581    } else {
582        // Looking for ElseBody, but there doesn't need to be one.
583        molt_ok!() // temp
584    }
585}
586
587/// # incr *varName* ?*increment* ...?
588///
589/// Increments an integer variable by a value.
590pub fn cmd_incr(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
591    check_args(1, argv, 2, 3, "varName ?increment?")?;
592
593    let increment: MoltInt = if argv.len() == 3 {
594        argv[2].as_int()?
595    } else {
596        1
597    };
598
599    let new_value = increment
600        + interp
601            .var(&argv[1])
602            .and_then(|val| Ok(val.as_int()?))
603            .unwrap_or_else(|_| 0);
604
605    interp.set_var_return(&argv[1], new_value.into())
606}
607
608/// # info *subcommand* ?*arg*...?
609pub fn cmd_info(interp: &mut Interp, context_id: ContextID, argv: &[Value]) -> MoltResult {
610    interp.call_subcommand(context_id, argv, 1, &INFO_SUBCOMMANDS)
611}
612
613const INFO_SUBCOMMANDS: [Subcommand; 11] = [
614    Subcommand("args", cmd_info_args),
615    Subcommand("body", cmd_info_body),
616    Subcommand("cmdtype", cmd_info_cmdtype),
617    Subcommand("commands", cmd_info_commands),
618    Subcommand("complete", cmd_info_complete),
619    Subcommand("default", cmd_info_default),
620    Subcommand("exists", cmd_info_exists),
621    Subcommand("globals", cmd_info_globals),
622    Subcommand("locals", cmd_info_locals),
623    Subcommand("procs", cmd_info_procs),
624    Subcommand("vars", cmd_info_vars),
625];
626
627/// # info args *procname*
628pub fn cmd_info_args(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
629    check_args(2, argv, 3, 3, "procname")?;
630    interp.proc_args(&argv[2].as_str())
631}
632
633/// # info body *procname*
634pub fn cmd_info_body(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
635    check_args(2, argv, 3, 3, "procname")?;
636    interp.proc_body(&argv[2].as_str())
637}
638
639/// # info cmdtype *command*
640pub fn cmd_info_cmdtype(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
641    check_args(2, argv, 3, 3, "command")?;
642    interp.command_type(&argv[2].as_str())
643}
644
645/// # info commands ?*pattern*?
646pub fn cmd_info_commands(interp: &mut Interp, _: ContextID, _argv: &[Value]) -> MoltResult {
647    molt_ok!(Value::from(interp.command_names()))
648}
649
650/// # info default *procname* *arg* *varname*
651pub fn cmd_info_default(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
652    check_args(2, argv, 5, 5, "procname arg varname")?;
653
654    if let Some(val) = interp.proc_default(&argv[2].as_str(), &argv[3].as_str())? {
655        interp.set_var(&argv[4], val)?;
656        molt_ok!(1)
657    } else {
658        interp.set_var(&argv[4], Value::empty())?;
659        molt_ok!(0)
660    }
661}
662
663/// # info exists *varname*
664pub fn cmd_info_exists(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
665    check_args(2, argv, 3, 3, "varname")?;
666    Ok(interp.var_exists(&argv[2]).into())
667}
668
669/// # info complete *command*
670pub fn cmd_info_complete(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
671    check_args(2, argv, 3, 3, "command")?;
672
673    if interp.complete(argv[2].as_str()) {
674        molt_ok!(true)
675    } else {
676        molt_ok!(false)
677    }
678}
679
680/// # info globals
681/// TODO: Add glob matching as a feature, and provide optional pattern argument.
682pub fn cmd_info_globals(interp: &mut Interp, _: ContextID, _argv: &[Value]) -> MoltResult {
683    molt_ok!(Value::from(interp.vars_in_global_scope()))
684}
685
686/// # info locals
687/// TODO: Add glob matching as a feature, and provide optional pattern argument.
688pub fn cmd_info_locals(interp: &mut Interp, _: ContextID, _argv: &[Value]) -> MoltResult {
689    molt_ok!(Value::from(interp.vars_in_local_scope()))
690}
691
692/// # info procs ?*pattern*?
693pub fn cmd_info_procs(interp: &mut Interp, _: ContextID, _argv: &[Value]) -> MoltResult {
694    molt_ok!(Value::from(interp.proc_names()))
695}
696
697/// # info vars
698/// TODO: Add glob matching as a feature, and provide optional pattern argument.
699pub fn cmd_info_vars(interp: &mut Interp, _: ContextID, _argv: &[Value]) -> MoltResult {
700    molt_ok!(Value::from(interp.vars_in_scope()))
701}
702
703/// # join *list* ?*joinString*?
704///
705/// Joins the elements of a list with a string.  The join string defaults to " ".
706pub fn cmd_join(_interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
707    check_args(1, argv, 2, 3, "list ?joinString?")?;
708
709    let list = &argv[1].as_list()?;
710
711    let join_string = if argv.len() == 3 {
712        argv[2].to_string()
713    } else {
714        " ".to_string()
715    };
716
717    // TODO: Need to implement a standard join() method for MoltLists.
718    let list: Vec<String> = list.iter().map(|v| v.to_string()).collect();
719
720    molt_ok!(list.join(&join_string))
721}
722
723/// # lappend *varName* ?*value* ...?
724///
725/// Appends any number of values to a variable's list value, which need not
726/// initially exist.
727pub fn cmd_lappend(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
728    check_args(1, argv, 2, 0, "varName ?value ...?")?;
729
730    let var_result = interp.var(&argv[1]);
731
732    let mut list: MoltList = if var_result.is_ok() {
733        var_result.expect("got value").to_list()?
734    } else {
735        Vec::new()
736    };
737
738    let mut values = argv[2..].to_owned();
739    list.append(&mut values);
740    interp.set_var_return(&argv[1], Value::from(list))
741}
742
743/// # lindex *list* ?*index* ...?
744///
745/// Returns an element from the list, indexing into nested lists.
746pub fn cmd_lindex(_interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
747    check_args(1, argv, 2, 0, "list ?index ...?")?;
748
749    if argv.len() != 3 {
750        lindex_into(&argv[1], &argv[2..])
751    } else {
752        lindex_into(&argv[1], &*argv[2].as_list()?)
753    }
754}
755
756pub fn lindex_into(list: &Value, indices: &[Value]) -> MoltResult {
757    let mut value: Value = list.clone();
758
759    for index_val in indices {
760        let list = value.as_list()?;
761        let index = index_val.as_int()?;
762
763        value = if index < 0 || index as usize >= list.len() {
764            Value::empty()
765        } else {
766            list[index as usize].clone()
767        };
768    }
769
770    molt_ok!(value)
771}
772
773/// # list ?*arg*...?
774///
775/// Converts its arguments into a canonical list.
776pub fn cmd_list(_interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
777    // No arg check needed; can take any number.
778    molt_ok!(&argv[1..])
779}
780
781/// # llength *list*
782///
783/// Returns the length of the list.
784pub fn cmd_llength(_interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
785    check_args(1, argv, 2, 2, "list")?;
786
787    molt_ok!(argv[1].as_list()?.len() as MoltInt)
788}
789
790/// # pdump
791///
792/// Dumps profile data.  Developer use only.
793pub fn cmd_pdump(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
794    check_args(1, argv, 1, 1, "")?;
795
796    interp.profile_dump();
797
798    molt_ok!()
799}
800
801/// # pclear
802///
803/// Clears profile data.  Developer use only.
804pub fn cmd_pclear(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
805    check_args(1, argv, 1, 1, "")?;
806
807    interp.profile_clear();
808
809    molt_ok!()
810}
811
812/// # proc *name* *args* *body*
813///
814/// Defines a procedure.
815pub fn cmd_proc(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
816    check_args(1, argv, 4, 4, "name args body")?;
817
818    // FIRST, get the arguments
819    let name = argv[1].as_str();
820    let args = &*argv[2].as_list()?;
821
822    // NEXT, validate the argument specs
823    for arg in args {
824        let vec = arg.as_list()?;
825
826        if vec.is_empty() {
827            return molt_err!("argument with no name");
828        } else if vec.len() > 2 {
829            return molt_err!("too many fields in argument specifier \"{}\"", arg);
830        }
831    }
832
833    // NEXT, add the command.
834    interp.add_proc(name, args, &argv[3]);
835
836    molt_ok!()
837}
838
839/// # puts *string*
840///
841/// Outputs the string to stdout.
842///
843/// ## TCL Liens
844///
845/// * Does not support `-nonewline`
846/// * Does not support `channelId`
847pub fn cmd_puts(_interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
848    check_args(1, argv, 2, 2, "string")?;
849
850    println!("{}", argv[1]);
851    molt_ok!()
852}
853
854/// # rename *oldName* *newName*
855///
856/// Renames the command called *oldName* to have the *newName*.  If the
857/// *newName* is "", the command is destroyed.
858pub fn cmd_rename(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
859    check_args(1, argv, 3, 3, "oldName newName")?;
860
861    // FIRST, get the arguments
862    let old_name = argv[1].as_str();
863    let new_name = argv[2].as_str();
864
865    if !interp.has_command(old_name) {
866        return molt_err!("can't rename \"{}\": command doesn't exist", old_name);
867    }
868
869    // NEXT, rename or remove the command.
870    if new_name.is_empty() {
871        interp.remove_command(old_name);
872    } else {
873        interp.rename_command(old_name, new_name);
874    }
875
876    molt_ok!()
877}
878
879/// # return ?-code code? ?-level level? ?value?
880///
881/// Returns from a proc with the given *value*, which defaults to the empty result.
882/// See the documentation for **return** in The Molt Book for the option semantics.
883///
884/// ## TCL Liens
885///
886/// * Doesn't support all of TCL's fancy return machinery. Someday it will.
887pub fn cmd_return(_interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
888    check_args(1, argv, 1, 0, "?options...? ?value?")?;
889
890    // FIRST, set the defaults
891    let mut code = ResultCode::Okay;
892    let mut level: MoltInt = 1;
893    let mut error_code: Option<Value> = None;
894    let mut error_info: Option<Value> = None;
895
896    // NEXT, with no arguments just return.
897    if argv.len() == 1 {
898        return Err(Exception::molt_return_ext(
899            Value::empty(),
900            level as usize,
901            code,
902        ));
903    }
904
905    // NEXT, get the return value: the last argument, if there's an odd number of arguments
906    // after the command name.
907    let return_value: Value;
908
909    let opt_args: &[Value] = if argv.len() % 2 == 0 {
910        // odd number of args following the command name
911        return_value = argv[argv.len() - 1].clone();
912        &argv[1..argv.len() - 1]
913    } else {
914        // even number of args following the command name
915        return_value = Value::empty();
916        &argv[1..argv.len()]
917    };
918
919    // NEXT, Get any options
920    let mut queue = opt_args.iter();
921
922    while let Some(opt) = queue.next() {
923        // We built the queue to have an even number of arguments, and every option requires
924        // a value; so there can't be a missing option value.
925        let val = queue
926            .next()
927            .expect("missing option value: coding error in cmd_return");
928
929        match opt.as_str() {
930            "-code" => {
931                code = ResultCode::from_value(val)?;
932            }
933            "-errorcode" => {
934                error_code = Some(val.clone());
935            }
936            "-errorinfo" => {
937                error_info = Some(val.clone());
938            }
939            "-level" => {
940                // TODO: return better error:
941                // bad -level value: expected non-negative integer but got "{}"
942                level = val.as_int()?;
943            }
944            // TODO: In standard TCL there are no invalid options; all options are retained.
945            _ => return molt_err!("invalid return option: \"{}\"", opt),
946        }
947    }
948
949    // NEXT, return the result: normally a Return exception, but could be "Ok".
950    if code == ResultCode::Error {
951        Err(Exception::molt_return_err(
952            return_value,
953            level as usize,
954            error_code,
955            error_info,
956        ))
957    } else if level == 0 && code == ResultCode::Okay {
958        // Not an exception!j
959        Ok(return_value)
960    } else {
961        Err(Exception::molt_return_ext(
962            return_value,
963            level as usize,
964            code,
965        ))
966    }
967}
968
969/// # set *varName* ?*newValue*?
970///
971/// Sets variable *varName* to *newValue*, returning the value.
972/// If *newValue* is omitted, returns the variable's current value,
973/// returning an error if the variable is unknown.
974pub fn cmd_set(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
975    check_args(1, argv, 2, 3, "varName ?newValue?")?;
976
977    if argv.len() == 3 {
978        interp.set_var_return(&argv[1], argv[2].clone())
979    } else {
980        molt_ok!(interp.var(&argv[1])?)
981    }
982}
983
984/// # source *filename*
985///
986/// Sources the file, returning the result.
987pub fn cmd_source(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
988    check_args(1, argv, 2, 2, "filename")?;
989
990    let filename = argv[1].as_str();
991
992    match fs::read_to_string(filename) {
993        Ok(script) => interp.eval(&script),
994        Err(e) => molt_err!("couldn't read file \"{}\": {}", filename, e),
995    }
996}
997
998/// # string *subcommand* ?*arg*...?
999pub fn cmd_string(interp: &mut Interp, context_id: ContextID, argv: &[Value]) -> MoltResult {
1000    interp.call_subcommand(context_id, argv, 1, &STRING_SUBCOMMANDS)
1001}
1002
1003const STRING_SUBCOMMANDS: [Subcommand; 4] = [
1004    Subcommand("cat", cmd_string_cat),
1005    Subcommand("compare", cmd_string_compare),
1006    Subcommand("equal", cmd_string_equal),
1007    // Subcommand("first", cmd_string_todo),
1008    // Subcommand("index", cmd_string_todo),
1009    // Subcommand("last", cmd_string_todo),
1010    Subcommand("length", cmd_string_length),
1011    // Subcommand("map", cmd_string_todo),
1012    // Subcommand("range", cmd_string_todo),
1013    // Subcommand("replace", cmd_string_todo),
1014    // Subcommand("repeat", cmd_string_todo),
1015    // Subcommand("reverse", cmd_string_todo),
1016    // Subcommand("tolower", cmd_string_todo),
1017    // Subcommand("toupper", cmd_string_todo),
1018    // Subcommand("trim", cmd_string_todo),
1019    // Subcommand("trimleft", cmd_string_todo),
1020    // Subcommand("trimright", cmd_string_todo),
1021];
1022
1023/// Temporary: stub for string subcommands.
1024#[allow(unused)]
1025pub fn cmd_string_todo(_interp: &mut Interp, _: ContextID, _argv: &[Value]) -> MoltResult {
1026    molt_err!("TODO")
1027}
1028
1029/// string cat ?*arg* ...?
1030pub fn cmd_string_cat(_interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
1031    let mut buff = String::new();
1032
1033    for arg in &argv[2..] {
1034        buff.push_str(arg.as_str());
1035    }
1036
1037    molt_ok!(buff)
1038}
1039
1040/// string compare ?-nocase? ?-length length? string1 string2
1041pub fn cmd_string_compare(_interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
1042    check_args(2, argv, 4, 7, "?-nocase? ?-length length? string1 string2")?;
1043
1044    // FIRST, set the defaults.
1045    let arglen = argv.len();
1046    let mut nocase = false;
1047    let mut length: Option<MoltInt> = None;
1048
1049    // NEXT, get options
1050    let opt_args = &argv[2..arglen - 2];
1051    let mut queue = opt_args.iter();
1052
1053    while let Some(opt) = queue.next() {
1054        match opt.as_str() {
1055            "-nocase" => nocase = true,
1056            "-length" => {
1057                if let Some(val) = queue.next() {
1058                    length = Some(val.as_int()?);
1059                } else {
1060                    return molt_err!("wrong # args: should be \"string compare ?-nocase? ?-length length? string1 string2\"");
1061                }
1062            }
1063            _ => return molt_err!("bad option \"{}\": must be -nocase or -length", opt),
1064        }
1065    }
1066
1067    if nocase {
1068        let val1 = &argv[arglen - 2];
1069        let val2 = &argv[arglen - 1];
1070
1071        // TODO: *Not* the best way to do this; consider using the unicase crate.
1072        let val1 = Value::from(val1.as_str().to_lowercase());
1073        let val2 = Value::from(val2.as_str().to_lowercase());
1074
1075        molt_ok!(util::compare_len(val1.as_str(), val2.as_str(), length)?)
1076    } else {
1077        molt_ok!(util::compare_len(argv[arglen - 2].as_str(), argv[arglen - 1].as_str(), length)?)
1078    }
1079}
1080
1081/// string equal ?-nocase? ?-length length? string1 string2
1082pub fn cmd_string_equal(_interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
1083    check_args(2, argv, 4, 7, "?-nocase? ?-length length? string1 string2")?;
1084
1085    // FIRST, set the defaults.
1086    let arglen = argv.len();
1087    let mut nocase = false;
1088    let mut length: Option<MoltInt> = None;
1089
1090    // NEXT, get options
1091    let opt_args = &argv[2..arglen - 2];
1092    let mut queue = opt_args.iter();
1093
1094    while let Some(opt) = queue.next() {
1095        match opt.as_str() {
1096            "-nocase" => nocase = true,
1097            "-length" => {
1098                if let Some(val) = queue.next() {
1099                    length = Some(val.as_int()?);
1100                } else {
1101                    return molt_err!("wrong # args: should be \"string equal ?-nocase? ?-length length? string1 string2\"");
1102                }
1103            }
1104            _ => return molt_err!("bad option \"{}\": must be -nocase or -length", opt),
1105        }
1106    }
1107
1108    if nocase {
1109        let val1 = &argv[arglen - 2];
1110        let val2 = &argv[arglen - 1];
1111
1112        // TODO: *Not* the best way to do this; consider using the unicase crate.
1113        let val1 = Value::from(val1.as_str().to_lowercase());
1114        let val2 = Value::from(val2.as_str().to_lowercase());
1115
1116        let flag = util::compare_len(val1.as_str(), val2.as_str(), length)? == 0;
1117        molt_ok!(flag)
1118    } else {
1119        let flag = util::compare_len(argv[arglen - 2].as_str(), argv[arglen - 1].as_str(), length)? == 0;
1120        molt_ok!(flag)
1121    }
1122}
1123
1124/// string length *string*
1125pub fn cmd_string_length(_interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
1126    check_args(2, argv, 3, 3, "string")?;
1127
1128    let len: MoltInt = argv[2].as_str().chars().count() as MoltInt;
1129    molt_ok!(len)
1130}
1131
1132/// throw *type* *message*
1133///
1134/// Throws an error with the error code and message.
1135pub fn cmd_throw(_interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
1136    check_args(1, argv, 3, 3, "type message")?;
1137
1138    Err(Exception::molt_err2(argv[1].clone(), argv[2].clone()))
1139}
1140
1141/// # time *command* ?*count*?
1142///
1143/// Executes the command the given number of times, and returns the average
1144/// number of microseconds per iteration.  The *count* defaults to 1.
1145pub fn cmd_time(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
1146    check_args(1, argv, 2, 3, "command ?count?")?;
1147
1148    let command = &argv[1];
1149
1150    let count = if argv.len() == 3 {
1151        argv[2].as_int()?
1152    } else {
1153        1
1154    };
1155
1156    let start = Instant::now();
1157
1158    for _i in 0..count {
1159        let result = interp.eval_value(command);
1160        if result.is_err() {
1161            return result;
1162        }
1163    }
1164
1165    let span = start.elapsed();
1166
1167    let avg = if count > 0 {
1168        span.as_nanos() / (count as u128)
1169    } else {
1170        0
1171    } as MoltInt;
1172
1173    molt_ok!("{} nanoseconds per iteration", avg)
1174}
1175
1176/// # unset ?-nocomplain? *varName*
1177///
1178/// Removes the variable from the interpreter.  This is a no op if
1179/// there is no such variable.  The -nocomplain option is accepted for
1180/// compatible with standard TCL, but is never required.
1181pub fn cmd_unset(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
1182    check_args(1, argv, 1, 0, "?-nocomplain? ?--? ?name name name...?")?;
1183
1184    let mut options_ok = true;
1185
1186    for arg in argv {
1187        let var = arg.as_str();
1188
1189        if options_ok {
1190            if var == "--" {
1191                options_ok = false;
1192                continue;
1193            } else if var == "-nocomplain" {
1194                continue;
1195            }
1196        }
1197
1198        interp.unset_var(arg);
1199    }
1200
1201    molt_ok!()
1202}
1203
1204/// # while *test* *command*
1205///
1206/// A standard "while" loop.  *test* is a boolean expression; *command* is a script to
1207/// execute so long as the expression is true.
1208pub fn cmd_while(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
1209    check_args(1, argv, 3, 3, "test command")?;
1210
1211    while interp.expr_bool(&argv[1])? {
1212        let result = interp.eval_value(&argv[2]);
1213
1214        if let Err(exception) = result {
1215            match exception.code() {
1216                ResultCode::Break => break,
1217                ResultCode::Continue => (),
1218                _ => return Err(exception),
1219            }
1220        }
1221    }
1222
1223    molt_ok!()
1224}