Skip to main content

selene_lib/standard_library/
mod.rs

1mod lua_versions;
2pub mod v1;
3mod v1_upgrade;
4
5use std::{
6    borrow::{Borrow, Cow},
7    collections::{BTreeMap, HashMap},
8    fmt, io,
9};
10
11use once_cell::sync::OnceCell;
12use regex::{Captures, Regex};
13use serde::{
14    de::{self, Deserializer, Visitor},
15    ser::{SerializeMap, SerializeSeq, Serializer},
16    Deserialize, Serialize,
17};
18
19pub use lua_versions::*;
20
21lazy_static::lazy_static! {
22    static ref ANY_TABLE: BTreeMap<String, Field> = {
23        let mut map = BTreeMap::new();
24        map.insert("*".to_owned(), Field::from_field_kind(FieldKind::Any));
25        map
26    };
27}
28
29#[derive(Clone, Debug, PartialEq, Eq)]
30enum GlobalTreeField {
31    Key(String),
32    ReadOnlyField,
33}
34
35#[derive(Clone, Debug, PartialEq, Eq)]
36struct GlobalTreeNode {
37    children: BTreeMap<String, GlobalTreeNode>,
38    field: GlobalTreeField,
39}
40
41impl GlobalTreeNode {
42    fn field<'a>(&self, names_to_fields: &'a BTreeMap<String, Field>) -> &'a Field {
43        static READ_ONLY_FIELD: Field =
44            Field::from_field_kind(FieldKind::Property(PropertyWritability::ReadOnly));
45
46        match &self.field {
47            GlobalTreeField::Key(key) => names_to_fields
48                .get(key)
49                .unwrap_or_else(|| panic!("couldn't find {key} inside names_to_fields")),
50
51            GlobalTreeField::ReadOnlyField => &READ_ONLY_FIELD,
52        }
53    }
54}
55
56#[derive(Clone, Debug, Default, PartialEq, Eq)]
57struct GlobalTreeCache {
58    cache: BTreeMap<String, GlobalTreeNode>,
59
60    #[cfg(debug_assertions)]
61    last_globals_hash: u64,
62}
63
64#[profiling::function]
65fn extract_into_tree(
66    names_to_fields: &BTreeMap<String, Field>,
67) -> BTreeMap<String, GlobalTreeNode> {
68    let mut fields: BTreeMap<String, GlobalTreeNode> = BTreeMap::new();
69
70    for name in names_to_fields.keys() {
71        let mut current = &mut fields;
72
73        let mut split = name.split('.').collect::<Vec<_>>();
74        let final_name = split.pop().unwrap();
75
76        for segment in split {
77            current = &mut current
78                .entry(segment.to_string())
79                .or_insert_with(|| GlobalTreeNode {
80                    field: GlobalTreeField::ReadOnlyField,
81                    children: BTreeMap::new(),
82                })
83                .children;
84        }
85
86        let tree_field_key = GlobalTreeField::Key(name.to_owned());
87
88        if let Some(existing_segment) = current.get_mut(final_name) {
89            existing_segment.field = tree_field_key;
90        } else {
91            current.insert(
92                final_name.to_string(),
93                GlobalTreeNode {
94                    field: tree_field_key,
95                    children: BTreeMap::new(),
96                },
97            );
98        }
99    }
100
101    fields
102}
103
104#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
105#[serde(deny_unknown_fields)]
106pub struct StandardLibrary {
107    #[serde(default)]
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub base: Option<String>,
110
111    #[serde(default)]
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub name: Option<String>,
114
115    #[serde(default)]
116    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
117    pub globals: BTreeMap<String, Field>,
118
119    #[serde(default)]
120    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
121    pub structs: BTreeMap<String, BTreeMap<String, Field>>,
122
123    #[serde(default)]
124    pub lua_versions: Vec<lua_versions::LuaVersion>,
125
126    /// Internal, used for the Roblox standard library
127    #[serde(default)]
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub last_updated: Option<i64>,
130
131    #[serde(default)]
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub last_selene_version: Option<String>,
134
135    #[serde(default)]
136    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
137    pub roblox_classes: BTreeMap<String, RobloxClass>,
138
139    #[serde(skip)]
140    global_tree_cache: OnceCell<GlobalTreeCache>,
141}
142
143#[derive(Debug)]
144pub enum StandardLibraryError {
145    DeserializeTomlError(toml::de::Error),
146    DeserializeYamlError(serde_yaml::Error),
147    IoError(io::Error),
148}
149
150impl fmt::Display for StandardLibraryError {
151    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
152        match self {
153            StandardLibraryError::DeserializeTomlError(error) => {
154                write!(formatter, "deserialize toml error: {error}")
155            }
156
157            StandardLibraryError::DeserializeYamlError(error) => {
158                write!(formatter, "deserialize yaml error: {error}")
159            }
160
161            StandardLibraryError::IoError(error) => write!(formatter, "io error: {error}"),
162        }
163    }
164}
165
166impl std::error::Error for StandardLibraryError {
167    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
168        use StandardLibraryError::*;
169
170        match self {
171            DeserializeTomlError(error) => Some(error),
172            DeserializeYamlError(error) => Some(error),
173            IoError(error) => Some(error),
174        }
175    }
176}
177
178impl From<io::Error> for StandardLibraryError {
179    fn from(error: io::Error) -> Self {
180        StandardLibraryError::IoError(error)
181    }
182}
183
184impl StandardLibrary {
185    // This assumes globals has not changed, which it shouldn't by the time this is being used.
186    fn global_tree_cache(&self) -> &BTreeMap<String, GlobalTreeNode> {
187        // O(n) debug check to make sure globals doesn't change
188        #[cfg(debug_assertions)]
189        let hash = {
190            use std::{
191                collections::hash_map::DefaultHasher,
192                hash::{Hash, Hasher},
193            };
194
195            profiling::scope!("global_tree_cache: hash");
196
197            let mut hasher = DefaultHasher::new();
198            self.globals.hash(&mut hasher);
199            hasher.finish()
200        };
201
202        if let Some(cache) = self.global_tree_cache.get() {
203            profiling::scope!("global_tree_cache: cache hit");
204
205            #[cfg(debug_assertions)]
206            assert_eq!(
207                cache.last_globals_hash, hash,
208                "globals changed after global_tree_cache has already been created"
209            );
210
211            return &cache.cache;
212        }
213
214        profiling::scope!("global_tree_cache: cache not set");
215
216        &self
217            .global_tree_cache
218            .get_or_init(|| {
219                profiling::scope!("global_tree_cache: create cache");
220                GlobalTreeCache {
221                    cache: extract_into_tree(&self.globals),
222
223                    #[cfg(debug_assertions)]
224                    last_globals_hash: hash,
225                }
226            })
227            .cache
228    }
229
230    /// Find a global in the standard library through its name path.
231    /// Handles all of the following cases:
232    /// 1. "x.y" where `x.y` is explicitly defined
233    /// 2. "x.y" where `x.*` is defined
234    /// 3. "x.y" where `x` is a struct with a `y` or `*` field
235    /// 4. "x.y.z" where `x.*.z` or `x.*.*` is defined
236    /// 5. "x.y.z" where `x.y` or `x.*` is defined as "any"
237    /// 6. "x.y" resolving to a read only property if only "x.y.z" (or x.y.*) is explicitly defined
238    #[profiling::function]
239    pub fn find_global<S: Borrow<str>>(&self, names: &[S]) -> Option<&Field> {
240        assert!(!names.is_empty());
241
242        if let Some(explicit_global) = self.globals.get(&names.join(".")) {
243            profiling::scope!("find_global: explicit global");
244            return Some(explicit_global);
245        }
246
247        // TODO: This is really stupid lol
248        let mut last_extracted_struct;
249
250        let mut current = self.global_tree_cache();
251        let mut current_names_to_fields = &self.globals;
252
253        profiling::scope!("find_global: look through global tree cache");
254
255        for name in names.iter().take(names.len() - 1) {
256            let found_segment = current.get(name.borrow()).or_else(|| current.get("*"))?;
257            let field = found_segment.field(current_names_to_fields);
258
259            match &field.field_kind {
260                FieldKind::Any => {
261                    return Some(field);
262                }
263
264                FieldKind::Struct(struct_name) => {
265                    let strukt = self
266                        .structs
267                        .get(struct_name)
268                        .unwrap_or_else(|| panic!("struct `{struct_name}` not found"));
269
270                    last_extracted_struct = extract_into_tree(strukt);
271                    current_names_to_fields = strukt;
272                    current = &last_extracted_struct;
273                }
274
275                _ => {
276                    current = &found_segment.children;
277                }
278            }
279        }
280
281        current
282            .get(names.last().unwrap().borrow())
283            .or_else(|| current.get("*"))
284            .map(|node| node.field(current_names_to_fields))
285    }
286
287    pub fn global_has_fields(&self, name: &str) -> bool {
288        profiling::scope!("global_has_fields", name);
289        self.global_tree_cache().contains_key(name)
290    }
291
292    pub fn extend(&mut self, other: StandardLibrary) {
293        self.structs.extend(other.structs);
294
295        // let mut globals = other.globals.to_owned();
296        let mut globals: BTreeMap<String, Field> = other
297            .globals
298            .into_iter()
299            .filter(|(other_field_name, other_field)| {
300                other_field.field_kind != FieldKind::Removed
301                    && !matches!(
302                        self.globals.get(other_field_name),
303                        Some(Field {
304                            field_kind: FieldKind::Removed,
305                            ..
306                        })
307                    )
308            })
309            .collect();
310
311        globals.extend(
312            std::mem::take(&mut self.globals)
313                .into_iter()
314                .filter_map(|(key, value)| {
315                    if value.field_kind == FieldKind::Removed {
316                        None
317                    } else {
318                        Some((key, value))
319                    }
320                }),
321        );
322
323        // Intentionally not a merge, didn't seem valuable
324        if !other.lua_versions.is_empty() {
325            self.lua_versions = other.lua_versions;
326        }
327
328        self.globals = globals;
329    }
330
331    #[cfg(feature = "roblox")]
332    pub fn roblox_base() -> StandardLibrary {
333        StandardLibrary::from_builtin_name(
334            "roblox",
335            include_str!("../../default_std/roblox_base.yml"),
336        )
337        .expect("roblox_base.yml is missing")
338    }
339
340    fn from_builtin_name(name: &str, contents: &str) -> Option<StandardLibrary> {
341        let mut std = serde_yaml::from_str::<StandardLibrary>(contents).unwrap_or_else(|error| {
342            panic!("default standard library '{name}' failed deserialization: {error}")
343        });
344
345        if let Some(base_name) = &std.base {
346            let base = StandardLibrary::from_name(base_name);
347
348            std.extend(base.expect("built-in library based off of non-existent built-in"));
349        }
350
351        Some(std)
352    }
353
354    pub fn lua_version(
355        &self,
356    ) -> (
357        full_moon::LuaVersion,
358        Vec<lua_versions::LuaVersionError<'_>>,
359    ) {
360        let mut errors = Vec::new();
361
362        let mut lua_version = full_moon::LuaVersion::lua51();
363
364        for version in &self.lua_versions {
365            match version.to_lua_version() {
366                Ok(version) => lua_version |= version,
367                Err(error) => errors.push(error),
368            }
369        }
370
371        (lua_version, errors)
372    }
373}
374
375macro_rules! names {
376    {$($name:expr => $path:expr,)+} => {
377        impl StandardLibrary {
378            pub fn from_name(name: &str) -> Option<StandardLibrary> {
379                match name {
380                    $(
381                        $name => {
382                            StandardLibrary::from_builtin_name(name, include_str!($path))
383                        },
384                    )+
385
386                    _ => None
387                }
388            }
389
390            pub fn all_default_standard_libraries() -> &'static HashMap<&'static str, StandardLibrary> {
391                static CACHED_RESULT: OnceCell<HashMap<&'static str, StandardLibrary>> = OnceCell::new();
392
393                CACHED_RESULT.get_or_init(|| {
394                    let mut stds = HashMap::new();
395
396                    $(
397                        stds.insert(
398                            $name,
399                            StandardLibrary::from_name($name).unwrap(),
400                        );
401                    )+
402
403                    stds
404                })
405            }
406        }
407    };
408}
409
410names! {
411    "lua51" => "../../default_std/lua51.yml",
412    "lua52" => "../../default_std/lua52.yml",
413    "lua53" => "../../default_std/lua53.yml",
414    "luau" => "../../default_std/luau.yml",
415}
416
417fn is_default<T>(value: &T) -> bool
418where
419    T: Default + PartialEq<T>,
420{
421    value == &T::default()
422}
423
424#[derive(Clone, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
425pub struct FunctionBehavior {
426    #[serde(rename = "args")]
427    pub arguments: Vec<Argument>,
428
429    #[serde(default)]
430    #[serde(skip_serializing_if = "is_false")]
431    pub method: bool,
432
433    #[serde(default)]
434    #[serde(skip_serializing_if = "is_false")]
435    pub must_use: bool,
436}
437
438fn is_false(value: &bool) -> bool {
439    !value
440}
441
442#[derive(Clone, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
443pub struct Field {
444    #[serde(flatten)]
445    pub field_kind: FieldKind,
446
447    #[serde(default)]
448    #[serde(skip_serializing_if = "Option::is_none")]
449    pub deprecated: Option<Deprecated>,
450}
451
452impl Field {
453    pub const fn from_field_kind(field_kind: FieldKind) -> Self {
454        Self {
455            field_kind,
456            deprecated: None,
457        }
458    }
459
460    pub fn with_deprecated(self, deprecated: Option<Deprecated>) -> Self {
461        Self { deprecated, ..self }
462    }
463}
464
465#[derive(Clone, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
466#[serde(rename_all = "kebab-case")]
467pub struct Deprecated {
468    pub message: String,
469
470    // TODO: Validate proper %s
471    // TODO: Validate that a pattern is possible to reach (uses different # of parameters)
472    // TODO: Validate that parmeters match the number of arguments
473    // TODO: Validate that all numbers parse as u32
474    #[serde(default)]
475    pub replace: Vec<String>,
476}
477
478impl Deprecated {
479    fn regex_pattern() -> Regex {
480        Regex::new(r"%(%|(?P<number>[0-9]+)|(\.\.\.))").unwrap()
481    }
482
483    pub fn try_instead(&self, parameters: &[String]) -> Option<String> {
484        profiling::scope!("Deprecated::try_instead");
485
486        let regex_pattern = Deprecated::regex_pattern();
487
488        for replace_format in &self.replace {
489            let mut success = true;
490
491            let new_message = regex_pattern.replace_all(replace_format, |captures: &Captures| {
492                if let Some(number) = captures.name("number") {
493                    let number = match number.as_str().parse::<u32>() {
494                        Ok(number) => number,
495                        Err(_) => {
496                            success = false;
497                            return Cow::Borrowed("");
498                        }
499                    };
500
501                    if number > parameters.len() as u32 || number == 0 {
502                        success = false;
503                        return Cow::Borrowed("");
504                    }
505
506                    return Cow::Borrowed(&parameters[number as usize - 1]);
507                }
508
509                let capture = captures.get(1).unwrap();
510                match capture.as_str() {
511                    "%" => Cow::Borrowed("%"),
512                    "..." => Cow::Owned(parameters.join(", ")),
513                    other => unreachable!("Unexpected capture in deprecated formatting: {}", other),
514                }
515            });
516
517            if !success {
518                continue;
519            }
520
521            return Some(new_message.into_owned());
522        }
523
524        None
525    }
526}
527
528#[derive(Clone, Debug, Hash, PartialEq, Eq)]
529pub enum FieldKind {
530    Any,
531    Function(FunctionBehavior),
532    Property(PropertyWritability),
533    Struct(String),
534    Removed,
535}
536
537#[derive(Clone, Copy, Debug, PartialEq, Eq)]
538struct TrueOnly;
539
540impl<'de> Deserialize<'de> for TrueOnly {
541    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
542    where
543        D: Deserializer<'de>,
544    {
545        match bool::deserialize(deserializer) {
546            Ok(true) => Ok(TrueOnly),
547            _ => Err(de::Error::custom("expected `true`")),
548        }
549    }
550}
551
552impl Serialize for TrueOnly {
553    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
554    where
555        S: Serializer,
556    {
557        serializer.serialize_bool(true)
558    }
559}
560
561#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
562#[serde(untagged)]
563enum FieldKindSerde {
564    Any { any: TrueOnly },
565    Function(FunctionBehavior),
566    Removed { removed: TrueOnly },
567    Property { property: PropertyWritability },
568    Struct { r#struct: String },
569}
570
571impl<'de> Deserialize<'de> for FieldKind {
572    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
573    where
574        D: Deserializer<'de>,
575    {
576        let field_kind = FieldKindSerde::deserialize(deserializer)?;
577
578        Ok(match field_kind {
579            FieldKindSerde::Any { .. } => FieldKind::Any,
580            FieldKindSerde::Function(function_behavior) => FieldKind::Function(function_behavior),
581            FieldKindSerde::Removed { .. } => FieldKind::Removed,
582            FieldKindSerde::Property { property } => FieldKind::Property(property),
583            FieldKindSerde::Struct { r#struct } => FieldKind::Struct(r#struct),
584        })
585    }
586}
587
588impl Serialize for FieldKind {
589    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
590    where
591        S: Serializer,
592    {
593        let field_kind = match self {
594            FieldKind::Any => FieldKindSerde::Any { any: TrueOnly },
595            FieldKind::Function(function_behavior) => {
596                FieldKindSerde::Function(function_behavior.to_owned())
597            }
598            FieldKind::Removed => FieldKindSerde::Removed { removed: TrueOnly },
599            FieldKind::Property(property_writability) => FieldKindSerde::Property {
600                property: *property_writability,
601            },
602
603            FieldKind::Struct(r#struct) => FieldKindSerde::Struct {
604                r#struct: r#struct.to_owned(),
605            },
606        };
607
608        field_kind.serialize(serializer)
609    }
610}
611
612#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
613#[serde(rename_all = "kebab-case")]
614pub enum PropertyWritability {
615    // New fields can't be added, and entire variable can't be overridden
616    ReadOnly,
617    // New fields can be added and set, but variable itself cannot be redefined
618    NewFields,
619    // New fields can't be added, but entire variable can be overridden
620    OverrideFields,
621    // New fields can be added and entire variable can be overridden
622    FullWrite,
623}
624
625#[derive(Clone, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
626pub struct Argument {
627    #[serde(default)]
628    #[serde(skip_serializing_if = "is_default")]
629    pub required: Required,
630
631    #[serde(rename = "type")]
632    pub argument_type: ArgumentType,
633
634    #[serde(default)]
635    #[serde(skip_serializing_if = "is_default")]
636    pub observes: Observes,
637
638    #[serde(default)]
639    #[serde(skip_serializing_if = "Option::is_none")]
640    pub deprecated: Option<Deprecated>,
641}
642
643#[derive(Clone, Debug, Hash, PartialEq, Eq)]
644// TODO: Nilable types
645pub enum ArgumentType {
646    Any,
647    Bool,
648    Constant(Vec<String>),
649    Display(String),
650    // TODO: Optionally specify parameters
651    Function,
652    Nil,
653    Number,
654    String,
655    // TODO: Types for tables
656    Table,
657    // TODO: Support repeating types (like for string.char)
658    Vararg,
659}
660
661impl Serialize for ArgumentType {
662    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
663        match self {
664            &ArgumentType::Any
665            | &ArgumentType::Bool
666            | &ArgumentType::Function
667            | &ArgumentType::Nil
668            | &ArgumentType::Number
669            | &ArgumentType::String
670            | &ArgumentType::Table
671            | &ArgumentType::Vararg => serializer.serialize_str(&self.to_string()),
672
673            ArgumentType::Constant(constants) => {
674                let mut seq = serializer.serialize_seq(Some(constants.len()))?;
675                for constant in constants {
676                    seq.serialize_element(constant)?;
677                }
678                seq.end()
679            }
680
681            ArgumentType::Display(display) => {
682                let mut map = serializer.serialize_map(Some(1))?;
683                map.serialize_entry("display", display)?;
684                map.end()
685            }
686        }
687    }
688}
689
690impl<'de> Deserialize<'de> for ArgumentType {
691    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
692        deserializer.deserialize_any(ArgumentTypeVisitor)
693    }
694}
695
696struct ArgumentTypeVisitor;
697
698impl<'de> Visitor<'de> for ArgumentTypeVisitor {
699    type Value = ArgumentType;
700
701    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
702        formatter.write_str("an argument type or an array of constant strings")
703    }
704
705    fn visit_map<A: de::MapAccess<'de>>(self, mut access: A) -> Result<Self::Value, A::Error> {
706        let mut map: HashMap<String, String> = HashMap::new();
707
708        while let Some((key, value)) = access.next_entry()? {
709            map.insert(key, value);
710        }
711
712        if let Some(display) = map.remove("display") {
713            Ok(ArgumentType::Display(display))
714        } else {
715            Err(de::Error::custom(
716                "map value must have a `display` property",
717            ))
718        }
719    }
720
721    fn visit_seq<A: de::SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
722        let mut constants = Vec::new();
723
724        while let Some(value) = seq.next_element()? {
725            constants.push(value);
726        }
727
728        Ok(ArgumentType::Constant(constants))
729    }
730
731    fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
732        match value {
733            "any" => Ok(ArgumentType::Any),
734            "bool" => Ok(ArgumentType::Bool),
735            "function" => Ok(ArgumentType::Function),
736            "nil" => Ok(ArgumentType::Nil),
737            "number" => Ok(ArgumentType::Number),
738            "string" => Ok(ArgumentType::String),
739            "table" => Ok(ArgumentType::Table),
740            "..." => Ok(ArgumentType::Vararg),
741            other => Err(de::Error::custom(format!("unknown type {other}"))),
742        }
743    }
744}
745
746impl fmt::Display for ArgumentType {
747    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
748        match self {
749            ArgumentType::Any => write!(formatter, "any"),
750            ArgumentType::Bool => write!(formatter, "bool"),
751            ArgumentType::Constant(options) => write!(
752                formatter,
753                "{}",
754                // TODO: This gets pretty ugly with a lot of variants
755                options
756                    .iter()
757                    .map(|string| format!("\"{string}\""))
758                    .collect::<Vec<_>>()
759                    .join(", ")
760            ),
761            ArgumentType::Display(display) => write!(formatter, "{display}"),
762            ArgumentType::Function => write!(formatter, "function"),
763            ArgumentType::Nil => write!(formatter, "nil"),
764            ArgumentType::Number => write!(formatter, "number"),
765            ArgumentType::String => write!(formatter, "string"),
766            ArgumentType::Table => write!(formatter, "table"),
767            ArgumentType::Vararg => write!(formatter, "..."),
768        }
769    }
770}
771
772#[derive(Clone, Debug, Hash, PartialEq, Eq)]
773pub enum Required {
774    NotRequired,
775    Required(Option<String>),
776}
777
778impl Default for Required {
779    fn default() -> Self {
780        Required::Required(None)
781    }
782}
783
784impl<'de> Deserialize<'de> for Required {
785    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
786        deserializer.deserialize_any(RequiredVisitor)
787    }
788}
789
790impl Serialize for Required {
791    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
792        match self {
793            Required::NotRequired => serializer.serialize_bool(false),
794            Required::Required(None) => serializer.serialize_bool(true),
795            Required::Required(Some(message)) => serializer.serialize_str(message),
796        }
797    }
798}
799
800struct RequiredVisitor;
801
802impl Visitor<'_> for RequiredVisitor {
803    type Value = Required;
804
805    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
806        formatter.write_str("a boolean or a string message (when required)")
807    }
808
809    fn visit_bool<E: de::Error>(self, value: bool) -> Result<Self::Value, E> {
810        if value {
811            Ok(Required::Required(None))
812        } else {
813            Ok(Required::NotRequired)
814        }
815    }
816
817    fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
818        Ok(Required::Required(Some(value.to_owned())))
819    }
820}
821
822#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, Deserialize, Serialize)]
823#[serde(rename_all = "kebab-case")]
824pub enum Observes {
825    #[default]
826    ReadWrite,
827    Read,
828    Write,
829}
830
831#[derive(Clone, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
832pub struct RobloxClass {
833    pub superclass: String,
834    pub events: Vec<String>,
835    pub properties: Vec<String>,
836}
837
838impl RobloxClass {
839    pub fn has_event(&self, roblox_classes: &BTreeMap<String, RobloxClass>, event: &str) -> bool {
840        if self.events.iter().any(|other_event| other_event == event) {
841            true
842        } else if let Some(superclass) = roblox_classes.get(&self.superclass) {
843            superclass.has_event(roblox_classes, event)
844        } else {
845            false
846        }
847    }
848
849    pub fn has_property(
850        &self,
851        roblox_classes: &BTreeMap<String, RobloxClass>,
852        property: &str,
853    ) -> bool {
854        if self
855            .properties
856            .iter()
857            .any(|other_property| other_property == property)
858        {
859            true
860        } else if let Some(superclass) = roblox_classes.get(&self.superclass) {
861            superclass.has_property(roblox_classes, property)
862        } else {
863            false
864        }
865    }
866}
867
868#[cfg(test)]
869mod tests {
870    use super::*;
871
872    fn string_vec(strings: Vec<&str>) -> Vec<String> {
873        strings.into_iter().map(ToOwned::to_owned).collect()
874    }
875
876    #[test]
877    fn valid_serde() {
878        StandardLibrary::from_name("lua51").expect("lua51.toml wasn't found");
879        StandardLibrary::from_name("lua52").expect("lua52.toml wasn't found");
880    }
881
882    #[test]
883    fn deprecated_try_instead() {
884        let deprecated = Deprecated {
885            message: "You shouldn't see this".to_owned(),
886            replace: vec![
887                "eleven(%11)".to_owned(),
888                "four(%1, %2, %3, %4)".to_owned(),
889                "three(%1, %2, %3 %%3)".to_owned(),
890                "two(%1, %2)".to_owned(),
891                "one(%1)".to_owned(),
892            ],
893        };
894
895        assert_eq!(
896            deprecated.try_instead(&string_vec(vec!["a", "b", "c"])),
897            Some("three(a, b, c %3)".to_owned())
898        );
899
900        assert_eq!(
901            deprecated.try_instead(&string_vec(vec!["a", "b"])),
902            Some("two(a, b)".to_owned())
903        );
904
905        assert_eq!(
906            deprecated.try_instead(&string_vec(vec!["a"])),
907            Some("one(a)".to_owned())
908        );
909
910        assert_eq!(
911            deprecated.try_instead(&string_vec(vec![
912                "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11",
913            ])),
914            Some("eleven(11)".to_owned())
915        );
916
917        assert_eq!(deprecated.try_instead(&string_vec(vec![])), None);
918    }
919
920    #[test]
921    fn deprecated_varargs() {
922        let deprecated = Deprecated {
923            message: "You shouldn't see this".to_owned(),
924            replace: vec!["print(%...)".to_owned()],
925        };
926
927        assert_eq!(
928            deprecated.try_instead(&string_vec(vec!["a", "b", "c"])),
929            Some("print(a, b, c)".to_owned())
930        );
931    }
932}