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
use std::{fmt, rc::Rc};

use crate::{
    ll::{
        bytecode::{Environment, TraitIndex},
        codegen,
        gc::{Gc, Memory},
        value::create_trait,
    },
    Error, Hidden, LanguageErrorKind, MethodId, MethodParameterCount, Value,
};

/// Allows you to build traits programatically from Rust code.
///
/// Using traits is the intended way of calling Mica methods from Rust, because it's faster
/// (involving less indirections) and avoids calling unrelated methods unintentionally, because
/// every type defined in Mica must explicitly opt into implementing a trait.
///
/// # Limitations
///
/// Right now there exists no interface for implementing trait functions from within Rust for any
/// traits other than [the built-ins][crate::builtin_traits], and it's possible that there will
/// never be a way of doing so without violating type safety, which would increase the risk of bugs.
///
/// That said, this not a hard "no;" it just hasn't been researched nor implemented at the moment.
///
/// # Example
/// ```
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// use mica::{Engine, TraitBuilder, Value};
///
/// let mut engine = Engine::new();
///
/// let mut game_builder = engine.build_trait("Game")?;
/// let game_tick = game_builder.add_function("tick", 0)?;
/// let game_tick_count = game_builder.add_function("tick_count", 0)?;
/// let game = game_builder.build();
/// engine.set("Game", game);
///
/// let my_game: Value = engine
///     .start(
///         "game.mi",
///         r#" tick_count = 0
///
///             struct MyGame impl
///                 func new() constructor = nil
///
///                 as Game
///                     func tick() =
///                         tick_count = tick_count + 1
///
///                     func tick_count() = tick_count
///                 end
///             end
///             .new() "#
///     )?
///     .trampoline()?;
///
/// let expected_tick_count = 10_usize;
/// for _ in 0..expected_tick_count {
///     let _: Value = engine.call_method(my_game.clone(), game_tick, [])?;
/// }
/// let got_tick_count: usize = engine.call_method(my_game, game_tick_count, [])?;
/// assert_eq!(got_tick_count, expected_tick_count);
/// # Ok(())
/// # }
/// ```
pub struct TraitBuilder<'e> {
    pub(crate) inner: codegen::TraitBuilder<'e>,
    pub(crate) gc: &'e mut Memory,
}

impl<'e> TraitBuilder<'e> {
    /// Adds a new function requirement into the trait and returns its method ID, which can be used
    /// to call the function on values implementing the trait.
    pub fn add_function(&mut self, name: &str, arity: u8) -> Result<MethodId, Error> {
        let arity = MethodParameterCount::from_count_without_self(arity)
            .map_err(|_| Error::TooManyParametersInTraitMethod)?;
        self.inner.add_method(Rc::from(name), arity).map(MethodId).map_err(|e| match e {
            LanguageErrorKind::TooManyTraits => Error::TooManyTraits,
            LanguageErrorKind::TooManyFunctions => Error::TooManyFunctions,
            LanguageErrorKind::TooManyMethods => Error::TooManyMethods,
            LanguageErrorKind::TooManyParameters => Error::TooManyParametersInTraitMethod,
            _ => unreachable!(),
        })
    }

    /// Finishes building the trait and wraps it into a value.
    pub fn build(self) -> Value {
        let (trait_id, env) = self.inner.build();
        create_trait_value(env, self.gc, trait_id)
    }
}

impl<'e> fmt::Debug for TraitBuilder<'e> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("TraitBuilder").finish_non_exhaustive()
    }
}

pub(crate) fn create_trait_value(
    env: &mut Environment,
    gc: &mut Memory,
    trait_id: TraitIndex,
) -> Value {
    let instance = create_trait(env, gc, trait_id);
    let instance = unsafe { Gc::from_raw(instance) };
    Value::Trait(Hidden(instance))
}