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}