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}