molt_ng/
types.rs

1//! Public Type Declarations
2//!
3//! This module defines a number of types used throughout Molt's public API.
4//!
5//! The most important types are [`Value`], the type of data values in the Molt
6//! language, and [`MoltResult`], Molt's standard `Result<T,E>` type.  `MoltResult`
7//! is an alias for `Result<Value,Exception>`, where [`Exception`] contains the data
8//! relating to an exceptional return from a script.  The heart of `Exception` is the
9//! [`ResultCode`], which represents all of the ways a Molt script might return early:
10//! errors, explicit returns, breaks, and continues.
11//!
12//! [`MoltInt`], [`MoltFloat`], [`MoltList`], and [`MoltDict`] a/Displayre simple type aliases
13//! defining Molt's internal representation for integers, floats, and TCL lists and
14//! dictionaries.
15//!
16//! [`MoltResult`]: type.MoltResult.html
17//! [`Exception`]: struct.Exception.html
18//! [`MoltInt`]: type.MoltInt.html
19//! [`MoltFloat`]: type.MoltFloat.html
20//! [`MoltList`]: type.MoltList.html
21//! [`MoltDict`]: type.MoltDict.html
22//! [`ResultCode`]: enum.ResultCode.html
23//! [`Value`]: ../value/index.html
24//! [`interp`]: interp/index.html
25
26use crate::interp::Interp;
27pub use crate::value::Value;
28use indexmap::IndexMap;
29use std::fmt;
30use std::str::FromStr;
31
32// Molt Numeric Types
33
34/// The standard integer type for Molt code.
35///
36/// The interpreter uses this type internally for all Molt integer values.
37/// The primary reason for defining this as a type alias is future-proofing: at
38/// some point we may wish to replace `MoltInt` with a more powerful type that
39/// supports BigNums, or switch to `i128`.
40pub type MoltInt = i64;
41
42/// The standard floating point type for Molt code.
43///
44/// The interpreter uses this type internally for all Molt floating-point values.
45/// The primary reason for defining this as a type alias is future-proofing: at
46/// some point we may wish to replace `MoltFloat` with `f128`.
47pub type MoltFloat = f64;
48
49/// The standard list type for Molt code.
50///
51/// Lists are an important data structure, both in Molt code proper and in Rust code
52/// that implements and works with Molt commands.  A list is a vector of `Value`s.
53pub type MoltList = Vec<Value>;
54
55/// The standard dictionary type for Molt code.
56///
57/// A dictionary is a mapping from `Value` to `Value` that preserves the key insertion
58/// order.
59pub type MoltDict = IndexMap<Value, Value>;
60
61/// The standard `Result<T,E>` type for Molt code.
62///
63/// This is the return value of all Molt commands, and the most common return value
64/// throughout the Molt code base.  Every Molt command returns a [`Value`] (i.e., `Ok(Value)`)
65/// on success; if the command has no explicit return value, it returns the empty
66/// `Value`, a `Value` whose string representation is the empty string.
67///
68/// A Molt command returns an [`Exception`] (i.e., `Err(Exception)`) whenever the calling Molt
69/// script should return early: on error, when returning an explicit result via the
70/// `return` command, or when breaking out of a loop via the `break` or `continue`
71/// commands.  The precise nature of the return is indicated by the [`Exception`]'s
72/// [`ResultCode`].
73///
74/// Many of the functions in Molt's Rust API also return `MoltResult`, for easy use within
75/// Molt command definitions. Others return `Result<T,Exception>` for some type `T`; these
76/// are intended to produce a `T` value in Molt command definitions, while easily propagating
77/// errors up the call chain.
78///
79/// [`Exception`]: struct.Exception.html
80/// [`ResultCode`]: enum.ResultCode.html
81/// [`Value`]: ../value/index.html
82pub type MoltResult = Result<Value, Exception>;
83
84/// This enum represents the different kinds of [`Exception`] that result from
85/// evaluating a Molt script.
86///
87/// Client Rust code will usually see only the `Error` code; the others will most often be
88/// caught and handled within the interpreter.  However, client code may explicitly catch
89/// and handle `Break` and `Continue` (or application-defined codes) at both the Rust and
90/// the TCL level in order to implement application-specific control structures.  (See
91/// The Molt Book on the `return` and `catch` commands for more details on the TCL
92/// interface.)
93///
94/// [`Exception`]: struct.Exception.html
95
96#[derive(Debug, Clone, Copy, Eq, PartialEq)]
97pub enum ResultCode {
98    /// Value for `return -code` to indicate returning an `Ok(value)` higher up the stack.
99    /// Client code should rarely if ever need to refer to this constant explicitly.
100    Okay,
101
102    /// A Molt error.  The `Exception::value` is the error message for display to the
103    /// user.  The [`molt_err!`] and [`molt_throw!`] macros are usually used to produce
104    /// errors in client code; but the [`Exception`] struct has a number of methods that
105    /// give finer grained control.
106    ///
107    /// [`molt_err!`]: ../macro.molt_err.html
108    /// [`molt_throw!`]: ../macro.molt_throw.html
109    /// [`Exception`]: struct.Exception.html
110    Error,
111
112    /// An explicit return from a Molt procedure.  The `Exception::value` is the returned
113    /// value, or the empty value if `return` was called without a return value.  This result
114    /// will bubble up through one or more stack levels (i.e., enclosing TCL procedure calls)
115    /// and then yield the value as a normal `Ok` result.  If it is received when evaluating
116    /// an arbitrary script, i.e., if `return` is called outside of any procedure, the
117    /// interpreter will convert it into a normal `Ok` result.
118    ///
119    /// Clients will rarely need to interact with or reference this result code
120    /// explicitly, unless implementing application-specific control structures.  See
121    /// The Molt Book documentation for the `return` and `catch` command for the semantics.
122    Return,
123
124    /// A `break` in a Molt loop.  It will break out of the inmost enclosing loop in the usual
125    /// way.  If it is returned outside a loop (or some user-defined control structure that
126    /// supports `break`), the interpreter will convert it into an `Error`.
127    ///
128    /// Clients will rarely need to interact with or reference this result code
129    /// explicitly, unless implementing application-specific control structures.  See
130    /// The Molt Book documentation for the `return` and `catch` command for the semantics.
131    Break,
132
133    /// A `continue` in a Molt loop.  Execution will continue with the next iteration of
134    /// the inmost enclosing loop in the usual way.  If it is returned outside a loop (or
135    /// some user-defined control structure that supports `break`), the interpreter will
136    /// convert it into an error.
137    ///
138    /// Clients will rarely need to interact with or reference this result code
139    /// explicitly, unless implementing application-specific control structures.  See
140    /// The Molt Book documentation for the `return` and `catch` command for the semantics.
141    Continue,
142
143    /// A mechanism for defining application-specific result codes.
144    /// Clients will rarely need to interact with or reference this result code
145    /// explicitly, unless implementing application-specific control structures. See
146    /// The Molt Book documentation for the `return` and `catch` command for the semantics.
147    Other(MoltInt),
148}
149
150impl fmt::Display for ResultCode {
151    /// Formats a result code for use with the `return` command's `-code` option.
152    /// This is part of making `ResultCode` a valid external type for use with `Value`.
153    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
154        match self {
155            ResultCode::Okay => write!(f, "ok"),
156            ResultCode::Error => write!(f, "error"),
157            ResultCode::Return => write!(f, "return"),
158            ResultCode::Break => write!(f, "break"),
159            ResultCode::Continue => write!(f, "continue"),
160            ResultCode::Other(code) => write!(f, "{}", *code),
161        }
162    }
163}
164
165impl FromStr for ResultCode {
166    type Err = String;
167
168    /// Converts a symbolic or numeric result code into a `ResultCode`.  This is part
169    /// of making `ResultCode` a valid external type for use with `Value`.
170    fn from_str(value: &str) -> Result<Self, Self::Err> {
171        match value {
172            "ok" => return Ok(ResultCode::Okay),
173            "error" => return Ok(ResultCode::Error),
174            "return" => return Ok(ResultCode::Return),
175            "break" => return Ok(ResultCode::Break),
176            "continue" => return Ok(ResultCode::Continue),
177            _ => (),
178        }
179
180        match Value::get_int(value) {
181            Ok(num) => match num {
182                0 => Ok(ResultCode::Okay),
183                1 => Ok(ResultCode::Error),
184                2 => Ok(ResultCode::Return),
185                3 => Ok(ResultCode::Break),
186                4 => Ok(ResultCode::Continue),
187                _ => Ok(ResultCode::Other(num)),
188            },
189            Err(exception) => Err(exception.value().as_str().into()),
190        }
191    }
192}
193
194impl ResultCode {
195    /// A convenience: retrieves a result code string from the input `Value`
196    /// the enumerated value as an external type, converting it from
197    /// `Option<ResultCode>` into `Result<ResultCode,Exception>`.
198    ///
199    /// This is primarily intended for use by the `return` command; if you really
200    /// need it, you'd best be familiar with the implementation of `return` in
201    /// `command.rs`, as well as a good bit of `interp.rs`.
202    pub fn from_value(value: &Value) -> Result<Self, Exception> {
203        if let Some(x) = value.as_copy::<ResultCode>() {
204            Ok(x)
205        } else {
206            molt_err!("invalid result code: \"{}\"", value)
207        }
208    }
209
210    /// Returns the result code as an integer.
211    ///
212    /// This is primarily intended for use by the `catch` command.
213    pub fn as_int(&self) -> MoltInt {
214        match self {
215            ResultCode::Okay => 0,
216            ResultCode::Error => 1,
217            ResultCode::Return => 2,
218            ResultCode::Break => 3,
219            ResultCode::Continue => 4,
220            ResultCode::Other(num) => *num,
221        }
222    }
223}
224
225/// This struct represents the exceptional results of evaluating a Molt script, as
226/// used in [`MoltResult`].  It is often used as the `Err` type for other
227/// functions in the Molt API, so that these functions can easily return errors when used
228/// in the definition of Molt commands.
229///
230/// A Molt command or script can return a normal result, as indicated by
231/// [`MoltResult`]'s `Ok` variant, or it can return one of a number of exceptional results via
232/// `Err(Exception)`.  Exceptions bubble up the call stack in the usual way until
233/// caught. The different kinds of exceptional result are defined by the
234/// [`ResultCode`] enum.  Client code is primarily concerned with `ResultCode::Error`
235/// exceptions; other exceptions are handled by the interpreter and various control
236/// structure commands.  Except within application-specific control structure code (a rare
237/// bird), non-error exceptions can usually be ignored or converted to error exceptions—
238/// and the latter is usually done for you by the interpreter anyway.
239///
240/// [`ResultCode`]: enum.ResultCode.html
241/// [`MoltResult`]: type.MoltResult.html
242
243#[derive(Debug, Clone, Eq, PartialEq)]
244pub struct Exception {
245    /// The kind of exception
246    code: ResultCode,
247
248    /// The result value
249    value: Value,
250
251    /// The return -level value.  Should be non-zero only for `Return`.
252    level: usize,
253
254    /// The return -code value.  Should be equal to `code`, except for `code == Return`.
255    next_code: ResultCode,
256
257    /// The error info, if any.
258    error_data: Option<ErrorData>,
259}
260
261impl Exception {
262    /// Returns true if the exception is an error exception, and false otherwise.  In client
263    /// code, an Exception almost always will be an error; and unless you're implementing an
264    /// application-specific control structure can usually be treated as an error in any event.
265    ///
266    /// # Example
267    ///
268    /// ```
269    /// # use molt::types::*;
270    /// # use molt::Interp;
271    ///
272    /// let mut interp = Interp::new();
273    /// let input = "throw MYERR \"Error Message\"";
274    ///
275    /// match interp.eval(input) {
276    ///    Ok(val) => (),
277    ///    Err(exception) => {
278    ///        assert!(exception.is_error());
279    ///    }
280    /// }
281    /// ```
282    pub fn is_error(&self) -> bool {
283        self.code == ResultCode::Error
284    }
285
286    /// Returns the exception's error code, only if `is_error()`.
287    /// exception.
288    ///
289    /// # Panics
290    ///
291    /// Panics if the exception is not an error.
292    pub fn error_code(&self) -> Value {
293        self.error_data()
294            .expect("exception is not an error")
295            .error_code()
296    }
297
298    /// Returns the exception's error info, i.e., the human-readable error
299    /// stack trace, only if `is_error()`.
300    ///
301    /// # Panics
302    ///
303    /// Panics if the exception is not an error.
304    pub fn error_info(&self) -> Value {
305        self.error_data()
306            .expect("exception is not an error")
307            .error_info()
308    }
309
310    /// Gets the exception's [`ErrorData`], if any; the error data is available only when
311    /// the `code()` is `ResultCode::Error`.  The error data contains the error's error code
312    /// and stack trace information.
313    ///
314    /// # Example
315    ///
316    /// ```
317    /// # use molt::types::*;
318    /// # use molt::Interp;
319    ///
320    /// let mut interp = Interp::new();
321    /// let input = "throw MYERR \"Error Message\"";
322    ///
323    /// match interp.eval(input) {
324    ///    Ok(val) => (),
325    ///    Err(exception) => {
326    ///        if let Some(error_data) = exception.error_data() {
327    ///            assert_eq!(error_data.error_code(), "MYERR".into());
328    ///        }
329    ///    }
330    /// }
331    /// ```
332    ///
333    /// [`ErrorData`]: struct.ErrorData.html
334    pub fn error_data(&self) -> Option<&ErrorData> {
335        self.error_data.as_ref()
336    }
337
338    /// Gets the exception's result code.
339    ///
340    /// # Example
341    ///
342    /// This example shows catching all of the possible result codes.  Except in control
343    /// structure code, all of these but `ResultCode::Return` can usually be treated as
344    /// an error; and the caller of `Interp::eval` will only see them if the script being
345    /// called used the `return` command's `-level` option (or the Rust equivalent).
346    ///
347    /// ```
348    /// # use molt::types::*;
349    /// # use molt::Interp;
350    ///
351    /// let mut interp = Interp::new();
352    /// let input = "throw MYERR \"Error Message\"";
353    ///
354    /// match interp.eval(input) {
355    ///    Ok(val) => (),
356    ///    Err(exception) => {
357    ///        match exception.code() {
358    ///            ResultCode::Okay => { println!("Got an okay!") }
359    ///            ResultCode::Error => { println!("Got an error!") }
360    ///            ResultCode::Return => { println!("Got a return!") }
361    ///            ResultCode::Break => { println!("Got a break!")  }
362    ///            ResultCode::Continue => { println!("Got a continue!")  }
363    ///            ResultCode::Other(n) => { println!("Got an other {}", n)  }
364    ///        }
365    ///    }
366    /// }
367    /// ```
368    pub fn code(&self) -> ResultCode {
369        self.code
370    }
371
372    /// Gets the exception's value, i.e., the explicit return value or the error message.  In
373    /// client code, this will almost always be an error message.
374    ///
375    /// # Example
376    ///
377    /// This example shows catching all of the possible result codes.  Except in control
378    /// structure code, all of these but `ResultCode::Return` can usually be treated as
379    /// an error; and the caller of `Interp::eval` will only see them if the script being
380    /// called used the `return` command's `-level` option (or the Rust equivalent).
381    ///
382    /// ```
383    /// # use molt::types::*;
384    /// # use molt::Interp;
385    ///
386    /// let mut interp = Interp::new();
387    /// let input = "throw MYERR \"Error Message\"";
388    ///
389    /// match interp.eval(input) {
390    ///    Ok(val) => (),
391    ///    Err(exception) => {
392    ///        assert_eq!(exception.value(), "Error Message".into());
393    ///    }
394    /// }
395    /// ```
396    pub fn value(&self) -> Value {
397        self.value.clone()
398    }
399
400    /// Gets the exception's level.  The "level" code is set by the `return` command's
401    /// `-level` option.  See The Molt Book's `return` page for the semantics.  Client code
402    /// should rarely if ever need to refer to this.
403    pub fn level(&self) -> usize {
404        self.level
405    }
406
407    /// Gets the exception's "next" code (when `code == ResultCode::Return` only).  The
408    /// "next" code is set by the `return` command's `-code` option.  See The Molt Book's
409    /// `return` page for the semantics.  Client code should rarely if ever need to refer
410    /// to this.
411    pub fn next_code(&self) -> ResultCode {
412        self.next_code
413    }
414
415    /// Adds a line to the exception's error info, i.e., to its human readable stack trace.
416    /// This is for use by command definitions that execute a TCL script and wish to
417    /// add to the stack trace on error as an aid to debugging.
418    ///
419    /// # Example
420    ///
421    /// ```
422    /// # use molt::types::*;
423    /// # use molt::Interp;
424    ///
425    /// let mut interp = Interp::new();
426    /// let input = "throw MYERR \"Error Message\"";
427    /// assert!(my_func(&mut interp, &input).is_err());
428    ///
429    /// fn my_func(interp: &mut Interp, input: &str) -> MoltResult {
430    ///     // Evaluates the input; on error, adds some error info and rethrows.
431    ///     match interp.eval(input) {
432    ///        Ok(val) => Ok(val),
433    ///        Err(mut exception) => {
434    ///            if exception.is_error() {
435    ///                exception.add_error_info("in rustdoc example");
436    ///            }
437    ///            Err(exception)
438    ///        }
439    ///     }
440    /// }
441    /// ```
442    ///
443    /// # Panics
444    ///
445    /// Panics if the exception is not an error exception.
446    pub fn add_error_info(&mut self, line: &str) {
447        if let Some(data) = &mut self.error_data {
448            data.add_info(line);
449        } else {
450            panic!("add_error_info called for non-Error Exception");
451        }
452    }
453
454    /// Creates an `Error` exception with the given error message.  This is primarily
455    /// intended for use by the [`molt_err!`] macro, but it can also be used directly.
456    ///
457    /// # Example
458    ///
459    /// ```
460    /// # use molt::types::*;
461    ///
462    /// let ex = Exception::molt_err("error message".into());
463    /// assert!(ex.is_error());
464    /// assert_eq!(ex.value(), "error message".into());
465    /// ```
466    ///
467    /// [`molt_err`]: ../macro.molt_err.html
468    pub fn molt_err(msg: Value) -> Self {
469        let data = ErrorData::new(Value::from("NONE"), msg.as_str());
470
471        Self {
472            code: ResultCode::Error,
473            value: msg,
474            level: 0,
475            next_code: ResultCode::Error,
476            error_data: Some(data),
477        }
478    }
479
480    /// Creates an `Error` exception with the given error code and message.  An
481    /// error code is a `MoltList` that indicates the nature of the error.  Standard TCL
482    /// uses the error code to flag specific arithmetic and I/O errors; most other
483    /// errors have the code `NONE`.  At present Molt doesn't define any error codes
484    /// other than `NONE`, so this method is primarily for use by the `throw` command;
485    /// but use it if your code needs to provide an error code.
486    ///
487    /// # Example
488    ///
489    /// ```
490    /// # use molt::types::*;
491    ///
492    /// let ex = Exception::molt_err2("MYERR".into(), "error message".into());
493    /// assert!(ex.is_error());
494    /// assert_eq!(ex.error_code(), "MYERR".into());
495    /// assert_eq!(ex.value(), "error message".into());
496    /// ```
497    ///
498    /// [`molt_err`]: ../macro.molt_err.html
499    pub fn molt_err2(error_code: Value, msg: Value) -> Self {
500        let data = ErrorData::new(error_code, msg.as_str());
501
502        Self {
503            code: ResultCode::Error,
504            value: msg,
505            level: 0,
506            next_code: ResultCode::Error,
507            error_data: Some(data),
508        }
509    }
510
511    /// Creates a `Return` exception, with the given return value.  Return `Value::empty()`
512    /// if there is no specific result.
513    ///
514    /// This method is primarily for use by the `return` command, and should rarely if
515    /// ever be needed in client code.  If you fully understand the semantics of the `return` and
516    /// `catch` commands, you'll understand what this does and when you would want
517    /// to use it.  If you don't, you almost certainly don't need it.
518    pub fn molt_return(value: Value) -> Self {
519        Self {
520            code: ResultCode::Return,
521            value,
522            level: 1,
523            next_code: ResultCode::Okay,
524            error_data: None,
525        }
526    }
527
528    /// Creates an extended `Return` exception with the given return value, `-level`,
529    /// and `-code`. Return `Value::empty()` if there is no specific result.
530    ///
531    /// It's an error if level == 0 and next_code == Okay; that's
532    /// `Ok(value)` rather than an exception.
533    ///
534    /// This method is primarily for use by the `return` command, and should rarely if
535    /// ever be needed in client code.  If you fully understand the semantics of the `return` and
536    /// `catch` commands, you'll understand what this does and when you would want
537    /// to use it.  If you don't, you almost certainly don't need it.
538    pub fn molt_return_ext(value: Value, level: usize, next_code: ResultCode) -> Self {
539        assert!(level > 0 || next_code != ResultCode::Okay);
540
541        Self {
542            code: if level > 0 {
543                ResultCode::Return
544            } else {
545                next_code
546            },
547            value,
548            level,
549            next_code,
550            error_data: None,
551        }
552    }
553
554    /// Creates an exception that will produce an `Error` exception with the given data,
555    /// either immediately or some levels up the call chain.  This is usually used to
556    /// rethrow an existing error.
557    ///
558    /// This method is primarily for use by the `return` command, and should rarely if
559    /// ever be needed in client code.  If you fully understand the semantics of the `return` and
560    /// `catch` commands, you'll understand what this does and when you would want
561    /// to use it.  If you don't, you almost certainly don't need it.
562    pub fn molt_return_err(
563        msg: Value,
564        level: usize,
565        error_code: Option<Value>,
566        error_info: Option<Value>,
567    ) -> Self {
568        let error_code = error_code.unwrap_or_else(|| Value::from("NONE"));
569        let error_info = error_info.unwrap_or_else(Value::empty);
570
571        let data = ErrorData::rethrow(error_code, error_info.as_str());
572
573        Self {
574            code: if level == 0 {
575                ResultCode::Error
576            } else {
577                ResultCode::Return
578            },
579            value: msg,
580            level,
581            next_code: ResultCode::Error,
582            error_data: Some(data),
583        }
584    }
585
586    /// Creates a `Break` exception.
587    ///
588    /// This method is primarily for use by the `break` command, and should rarely if
589    /// ever be needed in client code.  If you fully understand the semantics of the `return` and
590    /// `catch` commands, you'll understand what this does and when you would want
591    /// to use it.  If you don't, you almost certainly don't need it.
592    pub fn molt_break() -> Self {
593        Self {
594            code: ResultCode::Break,
595            value: Value::empty(),
596            level: 0,
597            next_code: ResultCode::Break,
598            error_data: None,
599        }
600    }
601
602    /// Creates a `Continue` exception.
603    ///
604    /// This method is primarily for use by the `continue` command, and should rarely if
605    /// ever be needed in client code.  If you fully understand the semantics of the `return` and
606    /// `catch` commands, you'll understand what this does and when you would want
607    /// to use it.  If you don't, you almost certainly don't need it.
608    pub fn molt_continue() -> Self {
609        Self {
610            code: ResultCode::Continue,
611            value: Value::empty(),
612            level: 0,
613            next_code: ResultCode::Continue,
614            error_data: None,
615        }
616    }
617
618    /// Only when the ResultCode is Return:
619    ///
620    /// * Decrements the -level.
621    /// * If it's 0, sets code to -code.
622    ///
623    /// This is used in `Interp::eval_script` to implement the `return` command's
624    /// `-code` and  `-level` protocol.
625    pub(crate) fn decrement_level(&mut self) {
626        assert!(self.code == ResultCode::Return && self.level > 0);
627        self.level -= 1;
628        if self.level == 0 {
629            self.code = self.next_code;
630        }
631    }
632
633    /// This is used by the interpreter when accumulating stack trace information.
634    /// See Interp::eval_script.
635    pub(crate) fn is_new_error(&self) -> bool {
636        if let Some(data) = &self.error_data {
637            data.is_new()
638        } else {
639            false
640        }
641    }
642}
643
644/// This struct contains the error code and stack trace (i.e., the "error info" string)
645/// for `ResultCode::Error` exceptions.
646#[derive(Debug, Clone, Eq, PartialEq)]
647pub struct ErrorData {
648    /// The error code; defaults to "NONE"
649    error_code: Value,
650
651    /// The TCL stack trace.
652    stack_trace: Vec<String>,
653
654    /// Is this a new error?
655    is_new: bool,
656}
657
658impl ErrorData {
659    // Creates a new ErrorData given the error code and error message.
660    // The error data is marked as "new", meaning that the stack_trace is know to contain
661    // a single error message.
662    fn new(error_code: Value, error_msg: &str) -> Self {
663        Self {
664            error_code,
665            stack_trace: vec![error_msg.into()],
666            is_new: true,
667        }
668    }
669
670    // Creates a rethrown ErrorData given the error code and error info.
671    // The error data is marked as not-new, meaning that the stack_trace has
672    // been initialized with a partial stack trace, not just the first error message.
673    fn rethrow(error_code: Value, error_info: &str) -> Self {
674        Self {
675            error_code,
676            stack_trace: vec![error_info.into()],
677            is_new: false,
678        }
679    }
680
681    /// Returns the error code.
682    pub fn error_code(&self) -> Value {
683        self.error_code.clone()
684    }
685
686    /// Whether this has just been created, or the stack trace has been extended.
687    pub(crate) fn is_new(&self) -> bool {
688        self.is_new
689    }
690
691    /// Returns the human-readable stack trace as a string.
692    pub fn error_info(&self) -> Value {
693        Value::from(self.stack_trace.join("\n"))
694    }
695
696    /// Adds to the stack trace, which, having been extended, is no longer new.
697    pub(crate) fn add_info(&mut self, info: &str) {
698        self.stack_trace.push(info.into());
699        self.is_new = false;
700    }
701}
702
703/// A unique identifier, used to identify cached context data within a given
704/// interpreter.  For more information see the discussion of command definition
705/// and the context cache in [The Molt Book] and the [`interp`] module.
706///
707/// [The Molt Book]: https://wduquette.github.io/molt/
708/// [`interp`]: ../interp/index.html
709
710#[derive(Eq, PartialEq, Debug, Hash, Copy, Clone)]
711pub struct ContextID(pub(crate) u64);
712
713/// A function used to implement a binary Molt command. For more information see the
714/// discussion of command definition in [The Molt Book] and the [`interp`] module.
715///
716/// The command may retrieve its application context from the [`interp`]'s context cache
717/// if it was defined with a [`ContextID`].
718///
719/// The command function receives the interpreter, the context ID, and a slice
720/// representing the command and its arguments.
721///
722/// [The Molt Book]: https://wduquette.github.io/molt/
723/// [`interp`]: ../interp/index.html
724/// [`ContextID`]: struct.ContextID.html
725pub type CommandFunc = fn(&mut Interp, ContextID, &[Value]) -> MoltResult;
726
727/// A Molt command that has subcommands is called an _ensemble_ command.  In Rust code,
728/// the ensemble is defined as an array of `Subcommand` structs, each one mapping from
729/// a subcommand name to the implementing [`CommandFunc`].  For more information,
730/// see the discussion of command definition in [The Molt Book] and the [`interp`] module.
731///
732/// The tuple fields are the subcommand's name and implementing [`CommandFunc`].
733///
734/// [The Molt Book]: https://wduquette.github.io/molt/
735/// [`interp`]: ../interp/index.html
736/// [`CommandFunc`]: type.CommandFunc.html
737pub struct Subcommand(pub &'static str, pub CommandFunc);
738
739impl Subcommand {
740    /// Looks up a subcommand of an ensemble command by name in a table,
741    /// returning the usual error if it can't be found.  It is up to the
742    /// ensemble command to call the returned subcommand with the
743    /// appropriate arguments.  See the implementation of the `info`
744    /// command for an example.
745    ///
746    /// # TCL Notes
747    ///
748    /// * In standard TCL, subcommand lookups accept any unambiguous prefix of the
749    ///   subcommand name, as a convenience for interactive use.  Molt does not, as it
750    ///   is confusing when used in scripts.
751    pub fn find<'a>(
752        ensemble: &'a [Subcommand],
753        sub_name: &str,
754    ) -> Result<&'a Subcommand, Exception> {
755        for subcmd in ensemble {
756            if subcmd.0 == sub_name {
757                return Ok(subcmd);
758            }
759        }
760
761        let mut names = String::new();
762        names.push_str(ensemble[0].0);
763        let last = ensemble.len() - 1;
764
765        if ensemble.len() > 1 {
766            names.push_str(", ");
767        }
768
769        if ensemble.len() > 2 {
770            let vec: Vec<&str> = ensemble[1..last].iter().map(|x| x.0).collect();
771            names.push_str(&vec.join(", "));
772        }
773
774        if ensemble.len() > 1 {
775            names.push_str(", or ");
776            names.push_str(ensemble[last].0);
777        }
778
779        molt_err!(
780            "unknown or ambiguous subcommand \"{}\": must be {}",
781            sub_name,
782            &names
783        )
784    }
785}
786
787/// In TCL, variable references have two forms.  A string like "_some_var_(_some_index_)" is
788/// the name of an array element; any other string is the name of a scalar variable.  This
789/// struct is used when parsing variable references.  The `name` is the variable name proper;
790/// the `index` is either `None` for scalar variables or `Some(String)` for array elements.
791///
792/// The Molt [`interp`]'s variable access API usually handles this automatically.  Should a
793/// command need to distinguish between the two cases it can do so by using the
794/// the [`Value`] struct's `Value::as_var_name` method.
795///
796/// [`Value`]: ../value/index.html
797/// [`interp`]: ../interp/index.html
798#[derive(Debug, Eq, PartialEq)]
799pub struct VarName {
800    name: String,
801    index: Option<String>,
802}
803
804impl VarName {
805    /// Creates a scalar `VarName` given the variable's name.
806    pub fn scalar(name: String) -> Self {
807        Self { name, index: None }
808    }
809
810    /// Creates an array element `VarName` given the element's variable name and index string.
811    pub fn array(name: String, index: String) -> Self {
812        Self {
813            name,
814            index: Some(index),
815        }
816    }
817
818    /// Returns the parsed variable name.
819    pub fn name(&self) -> &str {
820        &self.name
821    }
822
823    /// Returns the parsed array index, if any.
824    pub fn index(&self) -> Option<&str> {
825        self.index.as_ref().map(|x| &**x)
826    }
827}
828
829#[cfg(test)]
830mod tests {
831    use super::*;
832
833    #[test]
834    fn test_result_code_as_string() {
835        // Tests Display for ResultCode
836        assert_eq!(Value::from_other(ResultCode::Okay).as_str(), "ok");
837        assert_eq!(Value::from_other(ResultCode::Error).as_str(), "error");
838        assert_eq!(Value::from_other(ResultCode::Return).as_str(), "return");
839        assert_eq!(Value::from_other(ResultCode::Break).as_str(), "break");
840        assert_eq!(Value::from_other(ResultCode::Continue).as_str(), "continue");
841        assert_eq!(Value::from_other(ResultCode::Other(5)).as_str(), "5");
842    }
843
844    #[test]
845    fn test_result_code_from_value() {
846        // Tests FromStr for ResultCode, from_value
847        assert_eq!(ResultCode::from_value(&"ok".into()), Ok(ResultCode::Okay));
848        assert_eq!(
849            ResultCode::from_value(&"error".into()),
850            Ok(ResultCode::Error)
851        );
852        assert_eq!(
853            ResultCode::from_value(&"return".into()),
854            Ok(ResultCode::Return)
855        );
856        assert_eq!(
857            ResultCode::from_value(&"break".into()),
858            Ok(ResultCode::Break)
859        );
860        assert_eq!(
861            ResultCode::from_value(&"continue".into()),
862            Ok(ResultCode::Continue)
863        );
864        assert_eq!(
865            ResultCode::from_value(&"5".into()),
866            Ok(ResultCode::Other(5))
867        );
868        assert!(ResultCode::from_value(&"nonesuch".into()).is_err());
869    }
870
871    #[test]
872    fn test_result_code_as_int() {
873        assert_eq!(ResultCode::Okay.as_int(), 0);
874        assert_eq!(ResultCode::Error.as_int(), 1);
875        assert_eq!(ResultCode::Return.as_int(), 2);
876        assert_eq!(ResultCode::Break.as_int(), 3);
877        assert_eq!(ResultCode::Continue.as_int(), 4);
878        assert_eq!(ResultCode::Other(5).as_int(), 5);
879    }
880
881    #[test]
882    fn test_error_data_new() {
883        let data = ErrorData::new("CODE".into(), "error message");
884
885        assert_eq!(data.error_code(), "CODE".into());
886        assert_eq!(data.error_info(), "error message".into());
887        assert!(data.is_new());
888    }
889
890    #[test]
891    fn test_error_data_rethrow() {
892        let data = ErrorData::rethrow("CODE".into(), "stack trace");
893
894        assert_eq!(data.error_code(), "CODE".into());
895        assert_eq!(data.error_info(), "stack trace".into());
896        assert!(!data.is_new());
897    }
898
899    #[test]
900    fn test_error_data_add_info() {
901        let mut data = ErrorData::new("CODE".into(), "error message");
902
903        assert_eq!(data.error_info(), "error message".into());
904        assert!(data.is_new());
905
906        data.add_info("next line");
907        assert_eq!(data.error_info(), "error message\nnext line".into());
908        assert!(!data.is_new());
909    }
910
911    #[test]
912    fn test_exception_molt_err() {
913        let mut exception = Exception::molt_err("error message".into());
914
915        assert_eq!(exception.code(), ResultCode::Error);
916        assert_eq!(exception.value(), "error message".into());
917        assert!(exception.is_error());
918        assert!(exception.error_data().is_some());
919
920        if let Some(data) = exception.error_data() {
921            assert_eq!(data.error_code(), "NONE".into());
922            assert_eq!(data.error_info(), "error message".into());
923        }
924
925        exception.add_error_info("from unit test");
926
927        if let Some(data) = exception.error_data() {
928            assert_eq!(data.error_info(), "error message\nfrom unit test".into());
929        }
930    }
931
932    #[test]
933    fn test_exception_molt_err2() {
934        let exception = Exception::molt_err2("CODE".into(), "error message".into());
935
936        assert_eq!(exception.code(), ResultCode::Error);
937        assert_eq!(exception.value(), "error message".into());
938        assert!(exception.is_error());
939        assert!(exception.error_data().is_some());
940
941        if let Some(data) = exception.error_data() {
942            assert_eq!(data.error_code(), "CODE".into());
943            assert_eq!(data.error_info(), "error message".into());
944        }
945    }
946
947    #[test]
948    fn test_exception_molt_return_err_level0() {
949        let exception = Exception::molt_return_err(
950            "error message".into(),
951            0,
952            Some("MYERR".into()),
953            Some("stack trace".into()),
954        );
955
956        assert_eq!(exception.code(), ResultCode::Error);
957        assert_eq!(exception.next_code(), ResultCode::Error);
958        assert_eq!(exception.level(), 0);
959        assert_eq!(exception.value(), "error message".into());
960        assert!(exception.is_error());
961        assert!(exception.error_data().is_some());
962
963        if let Some(data) = exception.error_data() {
964            assert_eq!(data.error_code(), "MYERR".into());
965            assert_eq!(data.error_info(), "stack trace".into());
966        }
967    }
968
969    #[test]
970    fn test_exception_molt_return_err_level2() {
971        let exception = Exception::molt_return_err(
972            "error message".into(),
973            2,
974            Some("MYERR".into()),
975            Some("stack trace".into()),
976        );
977
978        assert_eq!(exception.code(), ResultCode::Return);
979        assert_eq!(exception.next_code(), ResultCode::Error);
980        assert_eq!(exception.level(), 2);
981        assert_eq!(exception.value(), "error message".into());
982        assert!(!exception.is_error());
983        assert!(exception.error_data().is_some());
984
985        if let Some(data) = exception.error_data() {
986            assert_eq!(data.error_code(), "MYERR".into());
987            assert_eq!(data.error_info(), "stack trace".into());
988        }
989    }
990
991    #[test]
992    #[should_panic]
993    fn text_exception_add_error_info() {
994        let mut exception = Exception::molt_break();
995
996        exception.add_error_info("should panic; not an error exception");
997    }
998
999    #[test]
1000    fn test_exception_molt_return() {
1001        let exception = Exception::molt_return("result".into());
1002
1003        assert_eq!(exception.code(), ResultCode::Return);
1004        assert_eq!(exception.value(), "result".into());
1005        assert_eq!(exception.level(), 1);
1006        assert_eq!(exception.next_code(), ResultCode::Okay);
1007        assert!(!exception.is_error());
1008        assert!(!exception.error_data().is_some());
1009    }
1010
1011    #[test]
1012    fn test_exception_molt_return_ext() {
1013        let exception = Exception::molt_return_ext("result".into(), 2, ResultCode::Break);
1014
1015        assert_eq!(exception.code(), ResultCode::Return);
1016        assert_eq!(exception.value(), "result".into());
1017        assert_eq!(exception.level(), 2);
1018        assert_eq!(exception.next_code(), ResultCode::Break);
1019        assert!(!exception.is_error());
1020        assert!(!exception.error_data().is_some());
1021    }
1022
1023    #[test]
1024    fn test_exception_molt_break() {
1025        let exception = Exception::molt_break();
1026
1027        assert_eq!(exception.code(), ResultCode::Break);
1028        assert_eq!(exception.value(), "".into());
1029        assert!(!exception.is_error());
1030        assert!(!exception.error_data().is_some());
1031    }
1032
1033    #[test]
1034    fn test_exception_molt_continue() {
1035        let exception = Exception::molt_continue();
1036
1037        assert_eq!(exception.code(), ResultCode::Continue);
1038        assert_eq!(exception.value(), "".into());
1039        assert!(!exception.is_error());
1040        assert!(!exception.error_data().is_some());
1041    }
1042}