molt_forked/
macros.rs

1//! Convenience Macros
2//!
3//! This module contains macros for use by command authors.
4
5/// Returns an `Ok` `MoltResult`.
6///
7/// If called with no arguments, returns an empty value as the `Ok` result.
8/// If called with one argument, returns the argument as the `Ok` result, converting it
9/// to a value automatically.
10/// If called with two or more arguments, computes the `Ok` result using
11/// `format!()`; the first argument is naturally the format string.
12///
13/// # Examples
14///
15/// ```
16/// use molt::*;
17///
18/// // Return the empty result
19/// fn func1() -> MoltResult {
20///     // ...
21///     molt_ok!()
22/// }
23///
24/// assert_eq!(func1(), Ok(Value::empty()));
25///
26/// // Return an arbitrary value
27/// fn func2() -> MoltResult {
28///     // ...
29///     molt_ok!(17)
30/// }
31///
32/// assert_eq!(func2(), Ok(17.into()));
33///
34/// // Return a formatted value
35/// fn func3() -> MoltResult {
36///     // ...
37///     molt_ok!("The answer is {}", 17)
38/// }
39///
40/// assert_eq!(func3(), Ok("The answer is 17".into()));
41/// ```
42#[macro_export]
43macro_rules! molt_ok {
44    () => (
45        Ok($crate::Value::empty())
46    );
47    ($arg:expr) => (
48        Ok($crate::Value::from($arg))
49    );
50    ($($arg:tt)*) => (
51        Ok($crate::Value::from(format!($($arg)*)))
52    )
53}
54
55/// Returns an `Error` `MoltResult`.  The error message is formatted
56/// as with `format!()`.
57///
58/// If called with one argument, the single argument is used as the error message.
59/// If called with more than one argument, the first is a `format!()` format string,
60/// and the remainder are the values to format.
61///
62/// This macro wraps the [`Exception::molt_err`](types/struct.Exception.html#method.molt_err)
63/// method.
64///
65/// # Examples
66///
67/// ```
68/// use molt::*;
69///
70/// // Return a simple error message
71/// fn err1() -> MoltResult {
72///     // ...
73///     molt_err!("error message")
74/// }
75///
76/// let result = err1();
77/// assert!(result.is_err());
78///
79/// let exception = result.err().unwrap();
80/// assert!(exception.is_error());
81/// assert_eq!(exception.value(), "error message".into());
82///
83/// // Return a formatted error
84/// fn err2() -> MoltResult {
85///    // ...
86///    molt_err!("invalid value: {}", 17)
87/// }
88///
89/// let result = err2();
90/// assert!(result.is_err());
91///
92/// let exception = result.err().unwrap();
93/// assert!(exception.is_error());
94/// assert_eq!(exception.value(), "invalid value: 17".into());
95/// ```
96#[macro_export]
97macro_rules! molt_err {
98    ($arg:expr) => (
99        Err($crate::Exception::molt_err($crate::Value::from($arg)))
100    );
101    ($($arg:tt)*) => (
102        Err($crate::Exception::molt_err($crate::Value::from(format!($($arg)*))))
103    )
104}
105
106#[macro_export]
107macro_rules! molt_err_help {
108    ($arg:expr) => {{
109      let mut e = $crate::Exception::molt_err($crate::Value::from($arg));
110      e.to_help();
111      Err(e)
112    }};
113    ($($arg:tt)*) => {{
114      let mut e = $crate::Exception::molt_err($crate::Value::from(format!($($arg)*)));
115      e.to_help();
116      Err(e)
117    }}
118}
119
120#[macro_export]
121macro_rules! molt_err_uncompleted {
122    ($arg:expr) => {{
123      let mut e = $crate::Exception::molt_err($crate::Value::from($arg));
124      e.to_uncomplete();
125      Err(e)
126    }};
127    ($($arg:tt)*) => {{
128      let mut e = $crate::Exception::molt_err($crate::Value::from(format!($($arg)*)));
129      e.to_uncomplete();
130      Err(e)
131    }}
132}
133
134/// Returns an `Error` `MoltResult` with a specific error code.  The error message is formatted
135/// as with `format!()`.
136///
137/// The macro requires two or more arguments.  The first argument is always the error code.
138/// If called with two arguments, the second is the error message.
139/// If called with more than two arguments, the second is a `format!()` format string and
140/// the remainder are the values to format.
141///
142/// This macro wraps
143/// the [`Exception::molt_err2`](types/struct.Exception.html#method.molt_err2)
144/// method.
145///
146/// # Examples
147///
148/// ```
149/// use molt::*;
150///
151/// // Throw a simple error
152/// fn throw1() -> MoltResult {
153///     // ...
154///     molt_throw!("MYCODE", "error message")
155/// }
156///
157/// let result = throw1();
158/// assert!(result.is_err());
159///
160/// let exception = result.err().unwrap();
161/// assert!(exception.is_error());
162/// assert_eq!(exception.value(), "error message".into());
163/// assert_eq!(exception.error_code(), "MYCODE".into());
164///
165/// // Return a formatted error
166/// fn throw2() -> MoltResult {
167///    // ...
168///    molt_throw!("MYCODE", "invalid value: {}", 17)
169/// }
170///
171/// let result = throw2();
172/// assert!(result.is_err());
173///
174/// let exception = result.err().unwrap();
175/// assert!(exception.is_error());
176/// assert_eq!(exception.value(), "invalid value: 17".into());
177/// assert_eq!(exception.error_code(), "MYCODE".into());
178/// ```
179#[macro_export]
180macro_rules! molt_throw {
181    ($code:expr, $msg:expr) => (
182        Err($crate::Exception::molt_err2($crate::Value::from($code), $crate::Value::from($msg)))
183    );
184    ($code:expr, $($arg:tt)*) => (
185        Err($crate::Exception::molt_err2($crate::Value::from($code), $crate::Value::from(format!($($arg)*))))
186    )
187}
188
189#[macro_export]
190macro_rules! join_strings {
191  () => {
192      ""
193  };
194  ($a:expr $(,)?) => {
195      $a
196  };
197  ($a:expr, $b:expr $(,)?) => {
198      concat!($a, " or ", $b)
199  };
200  ($a:expr, $($rest:expr),+ $(,)?) => {
201      concat!($a, ", ", join_strings!($($rest),+))
202  };
203}
204
205/// A Molt command that has subcommands is called an _ensemble_ command.  In Rust code,
206/// the ensemble is defined as an array of `Subcommand` structs, each one mapping from
207/// a subcommand name to the implementing [`CommandFunc`].  For more information,
208/// see the discussion of command definition in [The Molt Book] and the [`interp`] module.
209///
210/// The tuple fields are the subcommand's name and implementing [`CommandFunc`].
211///
212/// [The Molt Book]: https://wduquette.github.io/molt/
213/// [`interp`]: ../interp/index.html
214/// [`CommandFunc`]: type.CommandFunc.html
215///
216/// Calls a subcommand of the current command, looking up its name in an array of
217/// `Subcommand` tuples.
218///
219/// The subcommand, if found, is called with the same `context_ids` and `argv` as its
220/// parent ensemble.  `subc` is the index of the subcommand's name in the `argv` array;
221/// in most cases it will be `1`, but it is possible to define subcommands with
222/// subcommands of their own.  The `subcommands` argument is a borrow of an array of
223/// `Subcommand` records, each defining a subcommand's name and `CommandFunc`.
224///
225/// If the subcommand name is found in the array, the matching `CommandFunc` is called.
226/// otherwise, the error message gives the ensemble syntax.  If an invalid subcommand
227/// name was provided, the error message includes the valid options.
228///
229/// See the implementation of the `array` command in `commands.rs` and the
230/// [module level documentation](index.html) for examples.
231#[macro_export]
232macro_rules! _gen_subcommand_generic {
233  ($subc:expr, [ $( ($cmd_name:tt, $cmd_func:expr$(,)?) ),* $(,)?] $(,)?) => {
234    {
235      #[inline]
236      fn f<Ctx:'static>(interp: &mut $crate::prelude::Interp<Ctx>, argv: &[$crate::prelude::Value]) -> $crate::prelude::MoltResult {
237        check_args($subc, argv, $subc + 1, 0, "subcommand ?arg ...?")?;
238        let sub_name = argv[$subc].as_str();
239        match sub_name {
240          $(
241            $cmd_name => $cmd_func(interp, argv),
242          )*
243          _ => molt_err!("unknown or ambiguous subcommand \"{}\", must be:\n{}.", sub_name, join_strings!( $($cmd_name,)* )),
244        }
245      }
246      f
247    }
248  }
249}
250
251/// A Molt command that has subcommands is called an _ensemble_ command.  In Rust code,
252/// the ensemble is defined as an array of `Subcommand` structs, each one mapping from
253/// a subcommand name to the implementing [`CommandFunc`].  For more information,
254/// see the discussion of command definition in [The Molt Book] and the [`interp`] module.
255///
256/// The tuple fields are the subcommand's name and implementing [`CommandFunc`].
257///
258/// [The Molt Book]: https://wduquette.github.io/molt/
259/// [`interp`]: ../interp/index.html
260/// [`CommandFunc`]: type.CommandFunc.html
261///
262/// Calls a subcommand of the current command, looking up its name in an array of
263/// `Subcommand` tuples.
264///
265/// The subcommand, if found, is called with the same `context_ids` and `argv` as its
266/// parent ensemble.  `subc` is the index of the subcommand's name in the `argv` array;
267/// in most cases it will be `1`, but it is possible to define subcommands with
268/// subcommands of their own.  The `subcommands` argument is a borrow of an array of
269/// `Subcommand` records, each defining a subcommand's name and `CommandFunc`.
270///
271/// If the subcommand name is found in the array, the matching `CommandFunc` is called.
272/// otherwise, the error message gives the ensemble syntax.  If an invalid subcommand
273/// name was provided, the error message includes the valid options.
274///
275/// See the implementation of the `array` command in `commands.rs` and the
276/// [module level documentation](index.html) for examples.
277#[macro_export]
278macro_rules! gen_subcommand {
279  ($ctx_type:ty, $subc:expr, [ $( ($cmd_name:tt, $cmd_space:tt, $cmd_func:expr, $cmd_help:expr$(,)?) ),* $(,)?] $(,)?) => {
280    {
281      #[inline]
282      fn f(interp: &mut $crate::prelude::Interp<$ctx_type>, argv: &[$crate::prelude::Value]) -> $crate::prelude::MoltResult {
283        check_args($subc, argv, $subc + 1, 0, "subcommand ?arg ...?")?;
284        let sub_name = argv[$subc].as_str();
285        const HELP_MSG: &str = join_helps_subcmd!( $( [$cmd_name,$cmd_space,$cmd_help], )* );
286        match sub_name {
287          $(
288            $cmd_name => $cmd_func(interp, argv),
289          )*
290          "-help" => molt_ok!("usage of{}:\n{}",argv[0..$subc].iter().map(|v|v.as_str()).collect::<Vec<&str>>().join(" "),HELP_MSG),
291          _ => molt_err_help!("unknown subcommand in \"{} {}\", usage:\n{}", argv[0..$subc].iter().map(|v|v.as_str()).collect::<Vec<&str>>().join(" "),sub_name,HELP_MSG ),
292        }
293      }
294      f
295    }
296  }
297}
298
299#[macro_export]
300macro_rules! join_helps_subcmd {
301  (  ) => {
302      ""
303  };
304  // Base case: single element, no trailing newline
305  ( [$first:expr, $second:expr, $third:expr]$(,)? ) => {
306      concat!("  ", $first, "  ", $second, $third, "\n  -help")
307  };
308  // Recursive case: multiple elements
309  ( [$first:expr, $second:expr, $third:expr], $( [$rest_first:expr, $rest_second:expr, $rest_third:expr] ),+$(,)? ) => {
310      concat!(
311        "  ", $first, "  ", $second, $third, "\n",
312        join_helps_subcmd!($( [$rest_first, $rest_second, $rest_third] ),+)
313      )
314  };
315}
316
317#[macro_export]
318macro_rules! join_helps {
319  (  ) => {
320      ""
321  };
322  // Base case: single element, no trailing newline
323  ( [$first:expr, $second:expr, $third:expr]$(,)? ) => {
324      concat!("  ", $first, "  ", $second, $third, "\n  help  [-all]")
325  };
326  // Recursive case: multiple elements
327  ( [$first:expr, $second:expr, $third:expr], $( [$rest_first:expr, $rest_second:expr, $rest_third:expr] ),+$(,)? ) => {
328      concat!(
329        "  ", $first, "  ", $second, $third, "\n",
330          join_helps!($( [$rest_first, $rest_second, $rest_third] ),+)
331      )
332  };
333}
334
335#[macro_export]
336macro_rules! gen_command {
337  ($ctx_type:ty, [ $( ($native_name:tt, $native_func:expr $(,)?) ),* $(,)?], [ $( ($embedded_name:tt, $embedded_space:tt, $embedded_func:expr, $embedded_help:tt $(,)?) ),* $(,)?] $(,)?) => {
338    $crate::prelude::Command::new(
339      {fn f(name: &str, interp: &mut $crate::prelude::Interp<$ctx_type>, argv: &[$crate::prelude::Value]) -> $crate::prelude::MoltResult {
340        const HELP_MSG: &str = join_helps!( $( [$embedded_name,$embedded_space,$embedded_help], )* );
341        match name {
342          // NOTICE: Default native commands
343          $crate::prelude::_APPEND => $crate::prelude::cmd_append(interp, argv),
344          $crate::prelude::_ARRAY => $crate::prelude::cmd_array(interp, argv),
345          $crate::prelude::_ASSERT_EQ => $crate::prelude::cmd_assert_eq(interp, argv),
346          $crate::prelude::_BREAK => $crate::prelude::cmd_break(interp, argv),
347          $crate::prelude::_CATCH => $crate::prelude::cmd_catch(interp, argv),
348          $crate::prelude::_CONTINUE => $crate::prelude::cmd_continue(interp, argv),
349          $crate::prelude::_DICT => $crate::prelude::cmd_dict(interp, argv),
350          $crate::prelude::_ERROR => $crate::prelude::cmd_error(interp, argv),
351          $crate::prelude::_EXPR => $crate::prelude::cmd_expr(interp, argv),
352          $crate::prelude::_FOR => $crate::prelude::cmd_for(interp, argv),
353          $crate::prelude::_FOREACH => $crate::prelude::cmd_foreach(interp, argv),
354          $crate::prelude::_GLOBAL => $crate::prelude::cmd_global(interp, argv),
355          $crate::prelude::_IF => $crate::prelude::cmd_if(interp, argv),
356          $crate::prelude::_INCR => $crate::prelude::cmd_incr(interp, argv),
357          $crate::prelude::_INFO => $crate::prelude::cmd_info(interp, argv),
358          $crate::prelude::_JOIN => $crate::prelude::cmd_join(interp, argv),
359          $crate::prelude::_LAPPEND => $crate::prelude::cmd_lappend(interp, argv),
360          $crate::prelude::_LINDEX => $crate::prelude::cmd_lindex(interp, argv),
361          $crate::prelude::_LIST => $crate::prelude::cmd_list(interp, argv),
362          $crate::prelude::_LLENGTH => $crate::prelude::cmd_llength(interp, argv),
363          $crate::prelude::_PROC => $crate::prelude::cmd_proc(interp, argv),
364          $crate::prelude::_PUTS => $crate::prelude::cmd_puts(interp, argv),
365          $crate::prelude::_RENAME => $crate::prelude::cmd_rename(interp, argv),
366          $crate::prelude::_RETURN => $crate::prelude::cmd_return(interp, argv),
367          $crate::prelude::_SET => $crate::prelude::cmd_set(interp, argv),
368          $crate::prelude::_STRING => $crate::prelude::cmd_string(interp, argv),
369          $crate::prelude::_THROW => $crate::prelude::cmd_throw(interp, argv),
370          $crate::prelude::_TIME => $crate::prelude::cmd_time(interp, argv),
371          $crate::prelude::_UNSET => $crate::prelude::cmd_unset(interp, argv),
372          $crate::prelude::_WHILE => $crate::prelude::cmd_while(interp, argv),
373          "help" => {
374            if let Some(v)= argv.get(1){
375              if v.as_str()=="-all"{
376                let proc_command_names = interp.proc_command_names();
377                if proc_command_names.is_empty(){
378                  return molt_ok!("usage of {}:\ntcl:\n  {}\n{}:\n{}", interp.name,interp.native_command_names(),interp.name,HELP_MSG);
379                }else{
380                  return molt_ok!("usage of {}:\ntcl:\n  {}\n{}:\n{}\nprocedure:\n  {}", interp.name,interp.native_command_names(),interp.name,HELP_MSG,proc_command_names);
381                }
382              }
383            }
384            molt_ok!("usage of {}:\n{}",interp.name,HELP_MSG)},
385          // NOTICE: Extra native commands
386          $(
387            $native_name => $native_func(interp, argv),
388          )*
389          // NOTICE: Embedded commands
390          $(
391            $embedded_name => $embedded_func(interp, argv),
392          )*
393          // NOTICE: Proc commands
394          other => {
395            if let Some(proc) = interp.get_proc(other) {
396              proc.clone().execute(interp, argv)
397            } else {
398              let proc_command_names = interp.proc_command_names();
399              if proc_command_names.is_empty(){
400                molt_err_help!("unknown command \"{}\", valid commands:\ntcl:\n  {}\n{}:\n{}", name,interp.native_command_names(),interp.name,HELP_MSG)
401              }else{
402                molt_err_help!("unknown command \"{}\", valid commands:\ntcl:\n  {}\n{}:\n{}\nprocedure:\n  {}", name,interp.native_command_names(),interp.name,HELP_MSG,proc_command_names)
403              }
404            }
405          }
406        }
407      }
408      f as fn(&str, &mut $crate::prelude::Interp<$ctx_type>, &[$crate::prelude::Value]) -> $crate::prelude::MoltResult
409      },
410      {fn f(name: &str, interp: &$crate::prelude::Interp<$ctx_type>) -> Option<$crate::prelude::CommandType> {
411        match name {
412          $crate::prelude::_APPEND => Some($crate::prelude::CommandType::Native),
413          $crate::prelude::_ARRAY => Some($crate::prelude::CommandType::Native),
414          $crate::prelude::_ASSERT_EQ => Some($crate::prelude::CommandType::Native),
415          $crate::prelude::_BREAK => Some($crate::prelude::CommandType::Native),
416          $crate::prelude::_CATCH => Some($crate::prelude::CommandType::Native),
417          $crate::prelude::_CONTINUE => Some($crate::prelude::CommandType::Native),
418          $crate::prelude::_DICT => Some($crate::prelude::CommandType::Native),
419          $crate::prelude::_ERROR => Some($crate::prelude::CommandType::Native),
420          $crate::prelude::_EXPR => Some($crate::prelude::CommandType::Native),
421          $crate::prelude::_FOR => Some($crate::prelude::CommandType::Native),
422          $crate::prelude::_FOREACH => Some($crate::prelude::CommandType::Native),
423          $crate::prelude::_GLOBAL => Some($crate::prelude::CommandType::Native),
424          $crate::prelude::_IF => Some($crate::prelude::CommandType::Native),
425          $crate::prelude::_INCR => Some($crate::prelude::CommandType::Native),
426          $crate::prelude::_INFO => Some($crate::prelude::CommandType::Native),
427          $crate::prelude::_JOIN => Some($crate::prelude::CommandType::Native),
428          $crate::prelude::_LAPPEND => Some($crate::prelude::CommandType::Native),
429          $crate::prelude::_LINDEX => Some($crate::prelude::CommandType::Native),
430          $crate::prelude::_LIST => Some($crate::prelude::CommandType::Native),
431          $crate::prelude::_LLENGTH => Some($crate::prelude::CommandType::Native),
432          $crate::prelude::_PROC => Some($crate::prelude::CommandType::Native),
433          $crate::prelude::_PUTS => Some($crate::prelude::CommandType::Native),
434          $crate::prelude::_RENAME => Some($crate::prelude::CommandType::Native),
435          $crate::prelude::_RETURN => Some($crate::prelude::CommandType::Native),
436          $crate::prelude::_SET => Some($crate::prelude::CommandType::Native),
437          $crate::prelude::_STRING => Some($crate::prelude::CommandType::Native),
438          $crate::prelude::_THROW => Some($crate::prelude::CommandType::Native),
439          $crate::prelude::_TIME => Some($crate::prelude::CommandType::Native),
440          $crate::prelude::_UNSET => Some($crate::prelude::CommandType::Native),
441          $crate::prelude::_WHILE => Some($crate::prelude::CommandType::Native),
442          $(
443            $native_name => Some($crate::prelude::CommandType::Native),
444          )*
445          $(
446            $embedded_name => Some($crate::prelude::CommandType::Embedded),
447          )*
448          other => {
449            if interp.contains_proc(other) {
450              Some($crate::prelude::CommandType::Proc)
451            } else {
452              None
453            }
454          }
455        }
456      }
457      f as fn(&str, &$crate::prelude::Interp<$ctx_type>) -> Option<$crate::prelude::CommandType>
458      },
459      &[
460        $crate::prelude::_APPEND,
461        $crate::prelude::_ARRAY,
462        $crate::prelude::_ASSERT_EQ,
463        $crate::prelude::_BREAK,
464        $crate::prelude::_CATCH,
465        $crate::prelude::_CONTINUE,
466        $crate::prelude::_DICT,
467        $crate::prelude::_ERROR,
468        $crate::prelude::_EXPR,
469        $crate::prelude::_FOR,
470        $crate::prelude::_FOREACH,
471        $crate::prelude::_GLOBAL,
472        $crate::prelude::_IF,
473        $crate::prelude::_INCR,
474        $crate::prelude::_INFO,
475        $crate::prelude::_JOIN,
476        $crate::prelude::_LAPPEND,
477        $crate::prelude::_LINDEX,
478        $crate::prelude::_LIST,
479        $crate::prelude::_LLENGTH,
480        $crate::prelude::_PROC,
481        $crate::prelude::_PUTS,
482        $crate::prelude::_RENAME,
483        $crate::prelude::_RETURN,
484        $crate::prelude::_SET,
485        $crate::prelude::_STRING,
486        $crate::prelude::_THROW,
487        $crate::prelude::_TIME,
488        $crate::prelude::_UNSET,
489        $crate::prelude::_WHILE,
490        $(
491            $native_name,
492        )*
493      ],
494      &[
495        $(
496          $embedded_name,
497        )*
498      ]
499    )
500  };
501}
502
503#[cfg(test)]
504mod tests {
505    use crate::*;
506
507    #[test]
508    fn test_molt_ok() {
509        let result: MoltResult = molt_ok!();
510        assert_eq!(Ok(Value::empty()), result);
511
512        let result: MoltResult = molt_ok!(5);
513        assert_eq!(Ok(Value::from(5)), result);
514
515        let result: MoltResult = molt_ok!("Five");
516        assert_eq!(Ok(Value::from("Five")), result);
517
518        let result: MoltResult = molt_ok!("The answer is {}.", 5);
519        assert_eq!(Ok(Value::from("The answer is 5.")), result);
520    }
521
522    #[test]
523    fn test_molt_err() {
524        check_err(molt_err!("error message"), "error message");
525        check_err(molt_err!("error {}", 5), "error 5");
526    }
527
528    #[test]
529    fn test_molt_throw() {
530        check_throw(molt_throw!("MYERR", "error message"), "MYERR", "error message");
531        check_throw(molt_throw!("MYERR", "error {}", 5), "MYERR", "error 5");
532    }
533
534    fn check_err(result: MoltResult, msg: &str) -> bool {
535        match result {
536            Err(exception) => exception.is_error() && exception.value() == msg.into(),
537            _ => false,
538        }
539    }
540
541    fn check_throw(result: MoltResult, code: &str, msg: &str) -> bool {
542        match result {
543            Err(exception) => {
544                exception.is_error()
545                    && exception.value() == msg.into()
546                    && exception.error_code() == code.into()
547            }
548            _ => false,
549        }
550    }
551}