cas_attrs/
lib.rs

1#![doc = include_str!("../README.md")]
2
3mod builtin;
4mod error_kind;
5
6use builtin::{Builtin, Radian};
7use error_kind::ErrorKindTarget;
8use proc_macro::TokenStream;
9use quote::quote;
10use syn::parse_macro_input;
11
12/// Derives the [`ErrorKind`] trait for the given item.
13///
14/// This trait can be derived for any kind of `struct` and `enum`.
15///
16/// The information of the error can be customized using the `error` attribute by adding the
17/// corresponding tags to it:
18/// ```
19/// use cas_attrs::ErrorKind;
20/// use cas_error::ErrorKind;
21///
22/// #[derive(Debug, ErrorKind)]
23/// #[error(message = "unexpected end of file", labels = ["add something here"])]
24/// pub struct Foo;
25/// ```
26///
27/// The following tags are available:
28///
29/// | Tag         | Description                                                                                                                                                      |
30/// | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
31/// | `message`   | The message displayed at the top of the error when it is displayed.                                                                                              |
32/// | `labels`    | A list of labels that point to the spans of the error. The first label will be associated with the first span, the second label with the second span, and so on. |
33/// | `help`      | Optional help text for the error, describing what the user can do to fix it.                                                                                     |
34/// | `note`      | Optional note text for the error, providing additional context for the error.                                                                                    |
35///
36/// The `message` and `help` tags accept an expression that can be converted to a [`String`], and
37/// the `labels` tag accepts an expression that can be converted to a [`Vec`] of [`String`]s. Each
38/// expression is actually evaluated within an associated function with access to `&self`, so the
39/// expression can use the members of the struct or determine which variant of the enum is being
40/// used in the output.
41///
42/// [`ErrorKind`]: https://docs.rs/cas-error/latest/cas_error/trait.ErrorKind.html
43#[proc_macro_derive(ErrorKind, attributes(error))]
44pub fn error_kind(item: TokenStream) -> TokenStream {
45    let target = parse_macro_input!(item as ErrorKindTarget);
46    quote! { #target }.into()
47}
48
49/// An attribute that implements the [`Builtin`] trait on `struct`s representing functions.
50///
51/// This attribute can be applied to any `struct` that has a static implementation through an
52/// associated function called `eval_static`. The attribute will use this function in the
53/// implementation of the [`Builtin`] trait to perform runtime type-checking of the arguments
54/// passed to the function.
55///
56/// The `eval_static` method can be implemented like any Rust function, with some limitations to
57/// the types of its parameters, to match the types that [`Value`] provides. These are the accepted
58/// types:
59///
60/// | Type      | Description                                                                                                              |
61/// | --------- | ------------------------------------------------------------------------------------------------------------------------ |
62/// | `Float`   | [`rug::Float`]: A floating-point value. Floats can freely coerce to `Complex`.                                           |
63/// | `Integer` | [`rug::Integer`]: An integer value. Integers can freely coerce to `Complex` or `Float`.                                  |
64/// | `Complex` | [`rug::Complex`]: A complex number value. Complex numbers can coerce to `Float` or `Integer` if the imaginary part is 0. |
65/// | `bool`    | [`bool`]: A boolean value.                                                                                               |
66/// | `()`      | [`()`](unit): The unit type, analogous to `()` in Rust.                                                                        |
67/// | `Value`   | Any value, regardless of type. The value will be left as a [`Value`] for the function to handle.                         |
68///
69/// In addition, any of these types can be wrapped in an [`Option`] to make the argument optional.
70/// Optional arguments should be placed at the end of the list of parameters, though the attribute
71/// does not enforce this.
72///
73/// For trigonometric functions, the attribute can be used to indicate that the function takes
74/// input in radians, or returns an output in radians. This is done by adding the `radian` tag to
75/// the attribute, with the value `input` or `output`. If the user's trigonometric mode does not
76/// match the function's declared mode (i.e. the user is in degree mode), the input or output will
77/// be automatically converted to the correct mode. See the example below for more context.
78///
79/// # Examples
80///
81/// ```no_compile
82/// extern crate cas_attrs;
83///
84/// use cas_attrs::builtin;
85/// use cas_compute::primitive::{complex, float};
86/// use cas_compute::numerical::{
87///     ctxt::{Ctxt, TrigMode},
88///     error::kind::{MissingArgument, TooManyArguments, TypeMismatch},
89/// };
90/// use rug::{Complex, Float};
91///
92/// /// Returns the absolute value of a floating-point number.
93/// pub struct Abs;
94///
95/// #[builtin]
96/// impl Abs {
97///     pub fn eval_static(n: Float) -> Float {
98///         n.abs()
99///     }
100/// }
101///
102/// /// Returns the logarithm to an arbitrary base.
103/// pub struct Log;
104///
105/// #[builtin]
106/// impl Log {
107///     pub fn eval_static(n: Complex, base: Option<Complex>) -> Complex {
108///         let base = base.unwrap_or(complex(10));
109///         n.ln() / base.ln()
110///     }
111/// }
112///
113/// /// Returns the arcsine of a value.
114/// pub struct Asin;
115///
116/// // the `asin` function on the `rug` crate always returns radians, so we annotate this function
117/// // with `#[builtin(radian = output)]` to indicate this
118/// //
119/// // if the context is in degree mode, the output of this function is automatically converted to
120/// // degrees
121/// #[builtin(radian = output)]
122/// impl Asin {
123///     pub fn eval_static(n: Complex) -> Complex {
124///        n.asin()
125///     }
126/// }
127/// ```
128///
129/// [`Builtin`]: https://docs.rs/cas-compute/latest/cas_compute/numerical/builtin/trait.Builtin.html
130/// [`Value`]: https://docs.rs/cas-compute/latest/cas_compute/numerical/value/enum.Value.html
131/// [`rug::Float`]: https://docs.rs/rug/latest/rug/struct.Float.html
132/// [`rug::Integer`]: https://docs.rs/rug/latest/rug/struct.Integer.html
133/// [`rug::Complex`]: https://docs.rs/rug/latest/rug/struct.Complex.html
134 // NOTE: this cannot be a derive macro, since we need to know information about the function
135 // signature; applying #[derive(Builtin)] to the marker struct does not provide that information
136#[proc_macro_attribute]
137pub fn builtin(attrs: TokenStream, item: TokenStream) -> TokenStream {
138    let radian = parse_macro_input!(attrs as Radian);
139    let builtin = parse_macro_input!(item as Builtin);
140    builtin.generate(radian).into()
141}