ad_astra/runtime/
invoke.rs

1////////////////////////////////////////////////////////////////////////////////
2// This file is part of "Ad Astra", an embeddable scripting programming       //
3// language platform.                                                         //
4//                                                                            //
5// This work is proprietary software with source-available code.              //
6//                                                                            //
7// To copy, use, distribute, or contribute to this work, you must agree to    //
8// the terms of the General License Agreement:                                //
9//                                                                            //
10// https://github.com/Eliah-Lakhin/ad-astra/blob/master/EULA.md               //
11//                                                                            //
12// The agreement grants a Basic Commercial License, allowing you to use       //
13// this work in non-commercial and limited commercial products with a total   //
14// gross revenue cap. To remove this commercial limit for one of your         //
15// products, you must acquire a Full Commercial License.                      //
16//                                                                            //
17// If you contribute to the source code, documentation, or related materials, //
18// you must grant me an exclusive license to these contributions.             //
19// Contributions are governed by the "Contributions" section of the General   //
20// License Agreement.                                                         //
21//                                                                            //
22// Copying the work in parts is strictly forbidden, except as permitted       //
23// under the General License Agreement.                                       //
24//                                                                            //
25// If you do not or cannot agree to the terms of this Agreement,              //
26// do not use this work.                                                      //
27//                                                                            //
28// This work is provided "as is", without any warranties, express or implied, //
29// except where such disclaimers are legally invalid.                         //
30//                                                                            //
31// Copyright (c) 2024 Ilya Lakhin (Илья Александрович Лахин).                 //
32// All rights reserved.                                                       //
33////////////////////////////////////////////////////////////////////////////////
34
35use std::{
36    fmt::{Display, Formatter},
37    hash::{Hash, Hasher},
38    mem::take,
39};
40
41use crate::{
42    report::system_panic,
43    runtime::{Cell, Ident, Origin, Provider, TypeHint},
44};
45
46/// Metadata for a function-like object.
47///
48/// Typically, this object describes the signatures of exported Rust functions
49/// and methods, but it can also describe a broader range of exported objects
50/// that support the
51/// [invocation operation](crate::runtime::Prototype::implements_invocation).
52///
53/// This object aids in the static semantic analysis of Script source code.
54///
55/// The [Display] implementation of this object renders a canonical, user-facing
56/// view of the function's signature, such as
57/// `fn foo(x: usize, y: bool) -> f32`.
58///
59/// Generally, you don't need to instantiate this object manually, unless your
60/// crate introduces new types of invokable objects. In such cases, you should
61/// store this object in a static context.
62///
63/// ```
64/// use ad_astra::{
65///     lady_deirdre::sync::Lazy,
66///     runtime::{InvocationMeta, Origin, Param, ScriptType},
67/// };
68///
69/// static INVOKE_META: Lazy<InvocationMeta> = Lazy::new(|| {
70///     InvocationMeta {
71///         name: Some("foo"),
72///         inputs: Some(vec![Param {
73///             name: None,
74///             hint: <usize>::type_meta().into(),
75///         }]),
76///         ..InvocationMeta::new(Origin::nil())
77///     }
78/// });
79///
80/// assert_eq!(INVOKE_META.name, Some("foo"));
81/// ```
82#[derive(Clone, Debug)]
83pub struct InvocationMeta {
84    /// The source code range in Rust or Script where the function signature was
85    /// introduced.
86    ///
87    /// Typically, the resulting [Origin] points to the Rust code.
88    pub origin: Origin,
89
90    /// The name of the function, if the function has a name.
91    pub name: Option<&'static str>,
92
93    /// The RustDoc documentation for this function, if available.
94    pub doc: Option<&'static str>,
95
96    /// The type of the method's receiver (e.g., `self`, `&self`, and
97    /// similar Rust function parameters), if the function has a receiver.
98    pub receiver: Option<TypeHint>,
99
100    /// The signature of the function parameters, excluding the receiver,
101    /// if the signature metadata is available.
102    pub inputs: Option<Vec<Param>>,
103
104    /// The type of the object returned after the function invocation.
105    ///
106    /// If the return type is not specified, the `output` corresponds to the
107    /// [TypeHint::dynamic].
108    pub output: TypeHint,
109}
110
111impl PartialEq for &'static InvocationMeta {
112    #[inline(always)]
113    fn eq(&self, other: &Self) -> bool {
114        self as *const Self as usize == other as *const Self as usize
115    }
116}
117
118impl Eq for &'static InvocationMeta {}
119
120impl Hash for &'static InvocationMeta {
121    #[inline(always)]
122    fn hash<H: Hasher>(&self, state: &mut H) {
123        (self as *const Self as usize).hash(state)
124    }
125}
126
127impl Display for InvocationMeta {
128    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
129        match (&self.receiver, self.name) {
130            (None, Some(name)) => formatter.write_str(&format!("fn {}", name))?,
131
132            (Some(ty), Some(name)) => formatter.write_str(&format!("{}::{}", ty, name))?,
133
134            _ => formatter.write_str("fn")?,
135        };
136
137        if let Some(parameters) = &self.inputs {
138            formatter.write_str("(")?;
139
140            let mut is_first = true;
141
142            for parameter in parameters {
143                match is_first {
144                    true => is_first = false,
145                    false => formatter.write_str(", ")?,
146                }
147
148                Display::fmt(parameter, formatter)?;
149            }
150
151            formatter.write_str(")")?;
152        }
153
154        if !self.output.is_nil() {
155            formatter.write_str(" -> ")?;
156            Display::fmt(&self.output, formatter)?;
157        }
158
159        Ok(())
160    }
161}
162
163impl InvocationMeta {
164    /// Creates a new invocation metadata object with all fields set to their
165    /// default values.
166    #[inline(always)]
167    pub fn new(origin: Origin) -> Self {
168        Self {
169            origin,
170            name: None,
171            doc: None,
172            receiver: None,
173            inputs: None,
174            output: TypeHint::dynamic(),
175        }
176    }
177
178    /// Returns the arity of the function, which is the number of function
179    /// parameters excluding the [receiver](Self::receiver).
180    ///
181    /// This is a helper function similar in semantics to
182    /// `self.inputs.as_ref().map(|inputs| inputs.len())`.
183    #[inline(always)]
184    pub fn arity(&self) -> Option<usize> {
185        self.inputs.as_ref().map(|inputs| inputs.len())
186    }
187}
188
189/// A description of a parameter in the [InvocationMeta].
190#[derive(Clone, Debug)]
191pub struct Param {
192    /// The name of the parameter. If omitted, the parameter is anonymous.
193    pub name: Option<Ident>,
194
195    /// The type of the parameter.
196    pub hint: TypeHint,
197}
198
199impl Display for Param {
200    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
201        match self.hint.is_dynamic() {
202            true => match &self.name {
203                None => formatter.write_str("_"),
204                Some(name) => formatter.write_fmt(format_args!("{name}")),
205            },
206
207            false => {
208                if let Some(name) = &self.name {
209                    formatter.write_fmt(format_args!("{name}: "))?;
210                }
211
212                formatter.write_fmt(format_args!("{}", self.hint))
213            }
214        }
215    }
216}
217
218/// A wrapper for a [Cell] that is intended to be an argument of a function or
219/// an operator.
220#[derive(Clone, Default)]
221pub struct Arg {
222    /// The range in the Rust or Script source code that spans the location of
223    /// the argument's application.
224    pub origin: Origin,
225
226    /// The actual argument data.
227    pub data: Cell,
228}
229
230impl Arg {
231    /// A convenient constructor for creating an Arg.
232    #[inline(always)]
233    pub fn new(origin: Origin, data: Cell) -> Self {
234        Self { origin, data }
235    }
236
237    /// Returns a borrowed [Provider] of the argument's `data` Cell.
238    ///
239    /// This function is useful for [downcasting](crate::runtime::Downcast) the
240    /// argument into a Rust reference:
241    ///
242    /// `<&usize>::downcast(origin, arg.provider())`.
243    ///
244    /// Note that downcasting may render the argument's `data` obsolete.
245    /// Therefore, you shouldn't use this Cell again. If needed, clone the [Arg]
246    /// or the `data` Cell of this Arg object.
247    #[inline(always)]
248    pub fn provider(&mut self) -> Provider {
249        Provider::Borrowed(&mut self.data)
250    }
251
252    /// Returns an owned [Provider] of the argument's `data` Cell.
253    ///
254    /// This function is useful for [downcasting](crate::runtime::Downcast) the
255    /// argument into a Rust-owned data:
256    ///
257    /// `<usize>::downcast(origin, arg.into_provider())`.
258    #[inline(always)]
259    pub fn into_provider<'a>(self) -> Provider<'a> {
260        Provider::Owned(self.data)
261    }
262
263    /// A helper function that splits an `Arg` into a tuple of `origin` and
264    /// `data`.
265    #[inline(always)]
266    pub fn split(self) -> (Origin, Cell) {
267        (self.origin, self.data)
268    }
269
270    /// A helper function that retrieves an argument by index from an array of
271    /// arguments.
272    ///
273    /// This function returns an Arg object at `arguments[index]`, replacing the
274    /// original Cell with [Cell::nil].
275    ///
276    /// ## Panics
277    ///
278    /// This function panics if the index is greater than or equal to the length
279    /// of `arguments`.
280    #[inline(always)]
281    pub fn take(arguments: &mut [Self], index: usize) -> Self {
282        take(&mut arguments[index])
283    }
284
285    /// An unsafe version of the [Arg::take] function.
286    ///
287    /// Unlike the safe `take` function, the `take_unchecked` function does not
288    /// check the `arguments` boundaries in production builds. As a result, it
289    /// does not panic if the `index` exceeds the length of the input array.
290    ///
291    /// ## Safety
292    ///
293    /// The `index` must be less than `arguments.len()`.
294    #[inline(always)]
295    pub unsafe fn take_unchecked(arguments: &mut [Self], index: usize) -> Self {
296        #[cfg(debug_assertions)]
297        {
298            if index >= arguments.len() {
299                let bounds = arguments.len();
300
301                system_panic!("Argument {index} index out of bounds({bounds}).",);
302            }
303        }
304
305        // Safety: Upheld by the caller.
306        take(unsafe { arguments.get_unchecked_mut(index) })
307    }
308}