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}