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
//! Ruby symbols.

use std::{
    convert::TryFrom,
    ffi::CStr,
    fmt,
};
use crate::{
    object::{NonNullObject, Ty},
    prelude::*,
    string::Encoding,
    ruby,
};

/// An instance of Ruby's `Symbol` class.
#[derive(Clone, Copy, Debug)]
#[repr(transparent)]
pub struct Symbol(NonNullObject);

impl AsRef<AnyObject> for Symbol {
    #[inline]
    fn as_ref(&self) -> &AnyObject { self.0.as_ref() }
}

impl From<Symbol> for AnyObject {
    #[inline]
    fn from(object: Symbol) -> AnyObject { object.0.into() }
}

impl PartialEq<AnyObject> for Symbol {
    #[inline]
    fn eq(&self, obj: &AnyObject) -> bool {
        self.as_any_object() == obj
    }
}

unsafe impl Object for Symbol {
    #[inline]
    fn cast<A: Object>(obj: A) -> Option<Self> {
        if obj.is_ty(Ty::SYMBOL) {
            unsafe { Some(Self::cast_unchecked(obj)) }
        } else {
            None
        }
    }

    #[inline]
    fn ty(self) -> Ty { Ty::SYMBOL }

    #[inline]
    fn is_ty(self, ty: Ty) -> bool { ty == Ty::SYMBOL }
}

impl fmt::Display for Symbol {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.as_any_object().fmt(f)
    }
}

impl From<String> for Symbol {
    #[inline]
    fn from(s: String) -> Symbol {
        SymbolId::from(s).into()
    }
}

impl From<&str> for Symbol {
    #[inline]
    fn from(s: &str) -> Symbol {
        SymbolId::from(s).into()
    }
}

impl TryFrom<Symbol> for &str {
    type Error = std::str::Utf8Error;

    #[inline]
    fn try_from(s: Symbol) -> Result<Self, Self::Error> {
        s.name().to_str()
    }
}

impl TryFrom<Symbol> for std::string::String {
    type Error = std::str::Utf8Error;

    #[inline]
    fn try_from(s: Symbol) -> Result<Self, Self::Error> {
        s.name().to_str().map(Into::into)
    }
}

impl Symbol {
    #[inline]
    pub(crate) fn _id(self) -> ruby::ID {
        unsafe { ruby::rb_sym2id(self.raw()) }
    }

    /// Returns an array of all the symbols currently in Ruby's symbol table.
    ///
    /// # Examples
    ///
    /// ```
    /// # rosy::vm::init().unwrap();
    /// assert!(rosy::Symbol::all().contains("eql?"));
    /// ```
    #[inline]
    pub fn all() -> Array<Self> {
        unsafe { Array::from_raw(ruby::rb_sym_all_symbols()) }
    }

    /// Returns an array of the names of global variables.
    ///
    /// # Examples
    ///
    /// ```
    /// # rosy::vm::init().unwrap();
    /// assert!(rosy::Symbol::global_vars().contains("$SAFE"));
    /// ```
    #[inline]
    pub fn global_vars() -> Array<Self> {
        unsafe { Array::from_raw(ruby::rb_f_global_variables()) }
    }

    /// Returns whether `name` is valid as a symbol value.
    ///
    /// # Examples
    ///
    /// ```
    /// # rosy::vm::init().unwrap();
    /// use rosy::Symbol;
    ///
    /// assert!(Symbol::is_valid("@hello"));
    ///
    /// assert!(!Symbol::is_valid("\0a"));
    /// assert!(!Symbol::is_valid("$"));
    /// assert!(!Symbol::is_valid("@"));
    /// assert!(!Symbol::is_valid(""));
    /// ```
    #[inline]
    pub fn is_valid(name: impl AsRef<[u8]>) -> bool {
        let name = name.as_ref();
        let ptr = name.as_ptr();
        let len = name.len();
        let enc = Encoding::utf8()._enc();
        unsafe { ruby::rb_enc_symname2_p(ptr as _, len as _, enc) != 0 }
    }

    /// Returns the identifier associated with this symbol.
    #[inline]
    pub fn id(self) -> SymbolId {
        SymbolId(self._id())
    }

    /// Returns the symbol's name as a nul-terminated C string.
    #[inline]
    pub fn name(self) -> &'static CStr {
        self.id().name()
    }
}

/// An identifier for a [`Symbol`](struct.Symbol.html).
///
/// # What is `Into<SymbolId>`?
///
/// This is a convenience trait implementation that allows for instantiating a
/// `SymbolId` from a Rust
/// [`&str`](https://doc.rust-lang.org/std/primitive.str.html) or
/// Ruby [`String`](../struct.String.html). With it, one can simply pass a
/// normal string literal into a function that takes some `impl Into<SymbolId>`.
/// For an example, look at [`Object::call`](../trait.Object.html#method.call).
#[derive(Clone, Copy)]
pub struct SymbolId(ruby::ID);

impl fmt::Debug for SymbolId {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_tuple("SymbolId")
            .field(&self.raw())
            .finish()
    }
}

impl From<String> for SymbolId {
    #[inline]
    fn from(s: String) -> SymbolId {
        unsafe { SymbolId(ruby::rb_intern_str(s.raw())) }
    }
}

impl From<&str> for SymbolId {
    #[inline]
    fn from(s: &str) -> SymbolId {
        let raw = unsafe { ruby::rb_intern3(
            s.as_ptr() as _,
            s.len() as _,
            Encoding::utf8()._enc(),
        ) };
        SymbolId(raw)
    }
}

impl From<Symbol> for SymbolId {
    #[inline]
    fn from(s: Symbol) -> Self {
        SymbolId(s._id())
    }
}

impl From<SymbolId> for Symbol {
    #[inline]
    fn from(id: SymbolId) -> Symbol {
        unsafe { Symbol::from_raw(ruby::rb_id2sym(id.raw())) }
    }
}

impl SymbolId {
    /// Returns the raw underlying ID.
    #[inline]
    pub fn raw(self) -> ruby::ID {
        self.0
    }

    /// Returns the symbol's name as a nul-terminated C string.
    #[inline]
    pub fn name(self) -> &'static CStr {
        unsafe { CStr::from_ptr(ruby::rb_id2name(self.raw())) }
    }
}