Skip to main content

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