1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
//! Janet function types.
use core::{
    fmt::{self, Display},
    marker::PhantomData,
    ptr,
};

#[cfg(not(feature = "std"))]
use core::fmt::Write;

#[cfg(feature = "std")]
use std::{
    error,
    io::{self, Write},
};

use evil_janet::{janet_pcall, JanetFunction as CJanetFunction};

use crate::{cjvg, Janet, JanetFiber, JanetSignal};

#[cjvg("1.12.2")]
pub use trystate::JanetTryState;

/// C function pointer that is accepted by Janet as a type.
pub type JanetCFunction = evil_janet::JanetCFunction;

/// raw C Function
pub type JanetRawCFunction =
    unsafe extern "C-unwind" fn(i32, *mut evil_janet::Janet) -> evil_janet::Janet;

/// Error type that happens when calling a [`JanetFunction`] on the Rust side.
#[derive(Debug)]
pub struct CallError<'data> {
    kind:   CallErrorKind,
    value:  Janet,
    signal: JanetSignal,
    fiber:  Option<JanetFiber<'data>>,
}

/// Kinds of errors of [`CallError`].
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[non_exhaustive]
pub enum CallErrorKind {
    /// Wrong number of parameters passed.
    Arity,
    /// Fail to run a [`JanetFunction`].
    Run,
    /// [`JanetFunction`] yielded. That is not a problem per see, but some methods may
    /// expect a [`JanetFunction`] to return instead of yielding a value.
    Yield,
}

impl<'data> CallError<'data> {
    #[inline]
    const fn new(
        kind: CallErrorKind, value: Janet, signal: JanetSignal, fiber: Option<JanetFiber<'data>>,
    ) -> Self {
        Self {
            kind,
            value,
            signal,
            fiber,
        }
    }

    /// Returns the kind of the error.
    #[inline]
    #[must_use = "this returns the result of the operation, without modifying the original"]
    pub const fn kind(&self) -> CallErrorKind {
        self.kind
    }

    /// Returns the error value.
    #[inline]
    #[must_use = "this returns the result of the operation, without modifying the original"]
    pub const fn value(&self) -> Janet {
        self.value
    }

    /// Returns the [`JanetSignal`] that caused the error.
    #[inline]
    #[must_use = "this returns the result of the operation, without modifying the original"]
    pub const fn signal(&self) -> JanetSignal {
        self.signal
    }

    /// Get a reference to the fiber that the error happened if it exists.
    #[inline]
    #[must_use]
    pub const fn fiber(&self) -> Option<&JanetFiber> {
        self.fiber.as_ref()
    }

    /// Get a exclusive reference to the fiber that the error happened if it exists.
    #[inline]
    pub fn fiber_mut(&mut self) -> Option<&mut JanetFiber<'data>> {
        self.fiber.as_mut()
    }

    /// Consume the error and return the fiber that the error happened if it exists.
    #[inline]
    #[must_use = "this consumes self, making it impossible to use afterwards"]
    pub const fn take_fiber(self) -> Option<JanetFiber<'data>> {
        self.fiber
    }

    /// Display the stacktrace in the given `output`
    #[cfg(feature = "std")]
    #[cfg_attr(_doc, doc(cfg(feature = "std")))]
    #[cfg_attr(feature = "inline-more", inline)]
    pub fn stacktrace<W: Write + ?Sized>(&mut self, output: &mut W) -> io::Result<()> {
        if let CallErrorKind::Run = self.kind {
            if let Some(ref mut fiber) = self.fiber {
                fiber.display_stacktrace(self.value);
            } else {
                writeln!(output, "There is no stacktrace.")?;
            }
        } else {
            writeln!(output, "There is no stacktrace.")?;
        }

        Ok(())
    }

    /// Display the stacktrace in the given `output`
    #[cfg(not(feature = "std"))]
    #[cfg_attr(feature = "inline-more", inline)]
    pub fn stacktrace<W: Write + ?Sized>(&mut self, output: &mut W) -> fmt::Result {
        if let CallErrorKind::Run = self.kind {
            if let Some(ref mut fiber) = self.fiber {
                fiber.display_stacktrace(self.value);
            } else {
                output.write_str("There is no stacktrace.\n")?;
            }
        } else {
            output.write_str("There is no stacktrace.\n")?;
        }

        Ok(())
    }
}

impl Display for CallError<'_> {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self.kind {
            CallErrorKind::Arity => Display::fmt(
                &format_args!("{}: Wrong number of arguments", self.value),
                f,
            ),
            CallErrorKind::Yield => f.pad(
                "This function can yield more than one result. In those cases it's recommended to \
                 create a JanetFiber to execute all its steps",
            ),
            CallErrorKind::Run { .. } => f.pad("Failed to execute the Janet function."),
        }
    }
}

#[cfg(feature = "std")]
#[cfg_attr(_doc, doc(cfg(feature = "std")))]
impl error::Error for CallError<'_> {}

/// A representation of a Janet function defined at the Janet side.
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)]
#[repr(transparent)]
pub struct JanetFunction<'data> {
    pub(crate) raw: *mut CJanetFunction,
    phantom: PhantomData<&'data ()>,
}

impl<'data> JanetFunction<'data> {
    /// Create a new [`JanetFunction`] with a `raw` pointer.
    ///
    /// # Safety
    /// This function do not check if the given `raw` is `NULL` or not. Use at your
    /// own risk.
    #[inline]
    pub const unsafe fn from_raw(raw: *mut CJanetFunction) -> Self {
        Self {
            raw,
            phantom: PhantomData,
        }
    }

    /// Execute the [`JanetFunction`] with the given arguments.
    ///
    /// **This function may trigger a GC collection.**
    ///
    /// If the executions was successful returns the output, otherwise return the
    /// [`CallError`] with information returned by the call.
    #[cfg_attr(feature = "inline-more", inline)]
    pub fn call(&mut self, args: impl AsRef<[Janet]>) -> Result<Janet, CallError<'data>> {
        let args = args.as_ref();
        let mut out = Janet::nil();
        let fiber = ptr::null_mut();
        let raw_sig = unsafe {
            janet_pcall(
                self.raw,
                args.len() as i32,
                args.as_ptr() as *const _,
                &mut out.inner,
                fiber,
            )
        };

        let sig = raw_sig.into();

        match sig {
            JanetSignal::Ok | JanetSignal::User9 => Ok(out),
            JanetSignal::Yield => Err(CallError::new(CallErrorKind::Yield, out, sig, None)),
            JanetSignal::Error if out == Janet::from("arity mismatch") => {
                Err(CallError::new(CallErrorKind::Arity, out, sig, None))
            },
            _ => {
                // SAFETY: We checked if the pointer are null
                let fiber = unsafe {
                    if fiber.is_null() || (*fiber).is_null() {
                        None
                    } else {
                        Some(JanetFiber::from_raw(*fiber))
                    }
                };
                Err(CallError::new(CallErrorKind::Run, out, sig, fiber))
            },
        }
    }

    /// Execute the [`JanetFunction`] with the given arguments wising the given `fiber`.
    ///
    /// **This function may trigger the a GC collection.**
    ///
    /// If the executions was successful returns the output, otherwise return the
    /// [`CallError`] with information returned by the call.
    #[cfg_attr(feature = "inline-more", inline)]
    pub fn call_with_fiber<'fiber>(
        &mut self, mut fiber: JanetFiber<'fiber>, args: impl AsRef<[Janet]>,
    ) -> Result<Janet, CallError<'fiber>> {
        let args = args.as_ref();
        let mut out = Janet::nil();
        let raw_sig = unsafe {
            janet_pcall(
                self.raw,
                args.len() as i32,
                args.as_ptr() as *const _,
                &mut out.inner,
                &mut fiber.raw,
            )
        };

        let sig = raw_sig.into();

        match sig {
            JanetSignal::Ok | JanetSignal::User9 => Ok(out),
            JanetSignal::Yield => Err(CallError::new(CallErrorKind::Yield, out, sig, None)),
            JanetSignal::Error if out == Janet::from("arity mismatch") => {
                Err(CallError::new(CallErrorKind::Arity, out, sig, None))
            },
            _ => {
                let fiber = if fiber.raw.is_null() {
                    None
                } else {
                    Some(unsafe { JanetFiber::from_raw(fiber.raw) })
                };
                Err(CallError::new(CallErrorKind::Run, out, sig, fiber))
            },
        }
    }

    /// Execute the [`JanetFunction`] with the given arguments.
    ///
    /// **This function can not trigger GC collection.**
    ///
    /// # Janet Panics
    /// Panics if anything goes wrong trying to call the function.
    #[cfg_attr(feature = "inline-more", inline)]
    pub fn call_or_panic(&mut self, args: impl AsRef<[Janet]>) -> Janet {
        let args = args.as_ref();

        unsafe { evil_janet::janet_call(self.raw, args.len() as i32, args.as_ptr() as *const _) }
            .into()
    }

    /// Return a raw pointer to the function raw structure.
    ///
    /// The caller must ensure that the function outlives the pointer this function
    /// returns, or else it will end up pointing to garbage.
    #[inline]
    #[must_use]
    pub const fn as_raw(&self) -> *const CJanetFunction {
        self.raw
    }

    /// Return a raw mutable pointer to the function raw structure.
    ///
    /// The caller must ensure that the function outlives the pointer this function
    /// returns, or else it will end up pointing to garbage.
    #[inline]
    pub fn as_mut_raw(&mut self) -> *mut CJanetFunction {
        self.raw
    }
}

impl fmt::Debug for JanetFunction<'_> {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.pad("JanetFunction")
    }
}

#[cjvg("1.12.2")]
mod trystate {
    use core::mem::MaybeUninit;

    use crate::{Janet, JanetSignal};

    /// A structure that holds the old and new states of the Janet VM.
    ///
    /// This can be used to execute a [`JanetCFunction`] and capture its Janet panics.
    ///
    /// [`JanetCFunction`]: ./types/struct.JanetCFunction.html
    pub struct JanetTryState {
        inner: evil_janet::JanetTryState,
    }

    impl JanetTryState {
        /// Initializes the state.
        #[inline]
        #[must_use = "function is a constructor associated function"]
        pub fn init() -> Self {
            let mut state = {
                let mut state = MaybeUninit::uninit();
                unsafe {
                    // SAFETY: C-FFI
                    evil_janet::janet_try_init(state.as_mut_ptr());

                    // SAFETY: The above function initializes the state, therefore it is initialized
                    // now
                    state.assume_init()
                }
            };

            state.payload = Janet::nil().into();

            Self { inner: state }
        }

        /// Check if the VM have a valid [`JanetFiber`](crate::JanetFiber).
        #[inline]
        #[must_use = "this returns the result of the operation, without modifying the original"]
        pub fn is_valid_to_run(&self) -> bool {
            !self.inner.vm_fiber.is_null()
        }

        /// Get the [`JanetSignal`] of the state without checking if the environment is
        /// set to catch Janet Panics.
        ///
        /// # Safety
        /// If this is called with the invalid environment to catch Janet Panics it will
        /// cause undefined behaviour.
        #[inline]
        pub unsafe fn signal_unchecked(&mut self) -> JanetSignal {
            let signal = evil_janet::_setjmp(self.inner.buf.as_mut_ptr());

            JanetSignal::from(signal as u32)
        }

        /// Get the [`JanetSignal`] of the state if the environment is set to catch Janet
        /// Panics.
        #[inline]
        pub fn signal(&mut self) -> Option<JanetSignal> {
            if self.is_valid_to_run() {
                Some(unsafe { self.signal_unchecked() })
            } else {
                None
            }
        }

        /// Get the output of the execution.
        #[inline]
        #[must_use = "this returns the result of the operation, without modifying the original"]
        pub fn payload(&self) -> Janet {
            self.inner.payload.into()
        }
    }

    impl Drop for JanetTryState {
        fn drop(&mut self) {
            unsafe { evil_janet::janet_restore(&mut self.inner) };
        }
    }
}