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(&self) -> (full_moon::LuaVersion, Vec<lua_versions::LuaVersionError>) {
355        let mut errors = Vec::new();
356
357        let mut lua_version = full_moon::LuaVersion::lua51();
358
359        for version in &self.lua_versions {
360            match version.to_lua_version() {
361                Ok(version) => lua_version |= version,
362                Err(error) => errors.push(error),
363            }
364        }
365
366        (lua_version, errors)
367    }
368}
369
370macro_rules! names {
371    {$($name:expr => $path:expr,)+} => {
372        impl StandardLibrary {
373            pub fn from_name(name: &str) -> Option<StandardLibrary> {
374                match name {
375                    $(
376                        $name => {
377                            StandardLibrary::from_builtin_name(name, include_str!($path))
378                        },
379                    )+
380
381                    _ => None
382                }
383            }
384
385            pub fn all_default_standard_libraries() -> &'static HashMap<&'static str, StandardLibrary> {
386                static CACHED_RESULT: OnceCell<HashMap<&'static str, StandardLibrary>> = OnceCell::new();
387
388                CACHED_RESULT.get_or_init(|| {
389                    let mut stds = HashMap::new();
390
391                    $(
392                        stds.insert(
393                            $name,
394                            StandardLibrary::from_name($name).unwrap(),
395                        );
396                    )+
397
398                    stds
399                })
400            }
401        }
402    };
403}
404
405names! {
406    "lua51" => "../../default_std/lua51.yml",
407    "lua52" => "../../default_std/lua52.yml",
408    "lua53" => "../../default_std/lua53.yml",
409    "luau" => "../../default_std/luau.yml",
410}
411
412fn is_default<T>(value: &T) -> bool
413where
414    T: Default + PartialEq<T>,
415{
416    value == &T::default()
417}
418
419#[derive(Clone, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
420pub struct FunctionBehavior {
421    #[serde(rename = "args")]
422    pub arguments: Vec<Argument>,
423
424    #[serde(default)]
425    #[serde(skip_serializing_if = "is_false")]
426    pub method: bool,
427
428    #[serde(default)]
429    #[serde(skip_serializing_if = "is_false")]
430    pub must_use: bool,
431}
432
433fn is_false(value: &bool) -> bool {
434    !value
435}
436
437#[derive(Clone, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
438pub struct Field {
439    #[serde(flatten)]
440    pub field_kind: FieldKind,
441
442    #[serde(default)]
443    #[serde(skip_serializing_if = "Option::is_none")]
444    pub deprecated: Option<Deprecated>,
445}
446
447impl Field {
448    pub const fn from_field_kind(field_kind: FieldKind) -> Self {
449        Self {
450            field_kind,
451            deprecated: None,
452        }
453    }
454
455    pub fn with_deprecated(self, deprecated: Option<Deprecated>) -> Self {
456        Self { deprecated, ..self }
457    }
458}
459
460#[derive(Clone, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
461#[serde(rename_all = "kebab-case")]
462pub struct Deprecated {
463    pub message: String,
464
465    // TODO: Validate proper %s
466    // TODO: Validate that a pattern is possible to reach (uses different # of parameters)
467    // TODO: Validate that parmeters match the number of arguments
468    // TODO: Validate that all numbers parse as u32
469    #[serde(default)]
470    pub replace: Vec<String>,
471}
472
473impl Deprecated {
474    fn regex_pattern() -> Regex {
475        Regex::new(r"%(%|(?P<number>[0-9]+)|(\.\.\.))").unwrap()
476    }
477
478    pub fn try_instead(&self, parameters: &[String]) -> Option<String> {
479        profiling::scope!("Deprecated::try_instead");
480
481        let regex_pattern = Deprecated::regex_pattern();
482
483        for replace_format in &self.replace {
484            let mut success = true;
485
486            let new_message = regex_pattern.replace_all(replace_format, |captures: &Captures| {
487                if let Some(number) = captures.name("number") {
488                    let number = match number.as_str().parse::<u32>() {
489                        Ok(number) => number,
490                        Err(_) => {
491                            success = false;
492                            return Cow::Borrowed("");
493                        }
494                    };
495
496                    if number > parameters.len() as u32 || number == 0 {
497                        success = false;
498                        return Cow::Borrowed("");
499                    }
500
501                    return Cow::Borrowed(&parameters[number as usize - 1]);
502                }
503
504                let capture = captures.get(1).unwrap();
505                match capture.as_str() {
506                    "%" => Cow::Borrowed("%"),
507                    "..." => Cow::Owned(parameters.join(", ")),
508                    other => unreachable!("Unexpected capture in deprecated formatting: {}", other),
509                }
510            });
511
512            if !success {
513                continue;
514            }
515
516            return Some(new_message.into_owned());
517        }
518
519        None
520    }
521}
522
523#[derive(Clone, Debug, Hash, PartialEq, Eq)]
524pub enum FieldKind {
525    Any,
526    Function(FunctionBehavior),
527    Property(PropertyWritability),
528    Struct(String),
529    Removed,
530}
531
532#[derive(Clone, Copy, Debug, PartialEq, Eq)]
533struct TrueOnly;
534
535impl<'de> Deserialize<'de> for TrueOnly {
536    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
537    where
538        D: Deserializer<'de>,
539    {
540        match bool::deserialize(deserializer) {
541            Ok(true) => Ok(TrueOnly),
542            _ => Err(de::Error::custom("expected `true`")),
543        }
544    }
545}
546
547impl Serialize for TrueOnly {
548    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
549    where
550        S: Serializer,
551    {
552        serializer.serialize_bool(true)
553    }
554}
555
556#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
557#[serde(untagged)]
558enum FieldKindSerde {
559    Any { any: TrueOnly },
560    Function(FunctionBehavior),
561    Removed { removed: TrueOnly },
562    Property { property: PropertyWritability },
563    Struct { r#struct: String },
564}
565
566impl<'de> Deserialize<'de> for FieldKind {
567    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
568    where
569        D: Deserializer<'de>,
570    {
571        let field_kind = FieldKindSerde::deserialize(deserializer)?;
572
573        Ok(match field_kind {
574            FieldKindSerde::Any { .. } => FieldKind::Any,
575            FieldKindSerde::Function(function_behavior) => FieldKind::Function(function_behavior),
576            FieldKindSerde::Removed { .. } => FieldKind::Removed,
577            FieldKindSerde::Property { property } => FieldKind::Property(property),
578            FieldKindSerde::Struct { r#struct } => FieldKind::Struct(r#struct),
579        })
580    }
581}
582
583impl Serialize for FieldKind {
584    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
585    where
586        S: Serializer,
587    {
588        let field_kind = match self {
589            FieldKind::Any => FieldKindSerde::Any { any: TrueOnly },
590            FieldKind::Function(function_behavior) => {
591                FieldKindSerde::Function(function_behavior.to_owned())
592            }
593            FieldKind::Removed => FieldKindSerde::Removed { removed: TrueOnly },
594            FieldKind::Property(property_writability) => FieldKindSerde::Property {
595                property: *property_writability,
596            },
597
598            FieldKind::Struct(r#struct) => FieldKindSerde::Struct {
599                r#struct: r#struct.to_owned(),
600            },
601        };
602
603        field_kind.serialize(serializer)
604    }
605}
606
607#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
608#[serde(rename_all = "kebab-case")]
609pub enum PropertyWritability {
610    // New fields can't be added, and entire variable can't be overridden
611    ReadOnly,
612    // New fields can be added and set, but variable itself cannot be redefined
613    NewFields,
614    // New fields can't be added, but entire variable can be overridden
615    OverrideFields,
616    // New fields can be added and entire variable can be overridden
617    FullWrite,
618}
619
620#[derive(Clone, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
621pub struct Argument {
622    #[serde(default)]
623    #[serde(skip_serializing_if = "is_default")]
624    pub required: Required,
625
626    #[serde(rename = "type")]
627    pub argument_type: ArgumentType,
628
629    #[serde(default)]
630    #[serde(skip_serializing_if = "is_default")]
631    pub observes: Observes,
632
633    #[serde(default)]
634    #[serde(skip_serializing_if = "Option::is_none")]
635    pub deprecated: Option<Deprecated>,
636}
637
638#[derive(Clone, Debug, Hash, PartialEq, Eq)]
639// TODO: Nilable types
640pub enum ArgumentType {
641    Any,
642    Bool,
643    Constant(Vec<String>),
644    Display(String),
645    // TODO: Optionally specify parameters
646    Function,
647    Nil,
648    Number,
649    String,
650    // TODO: Types for tables
651    Table,
652    // TODO: Support repeating types (like for string.char)
653    Vararg,
654}
655
656impl Serialize for ArgumentType {
657    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
658        match self {
659            &ArgumentType::Any
660            | &ArgumentType::Bool
661            | &ArgumentType::Function
662            | &ArgumentType::Nil
663            | &ArgumentType::Number
664            | &ArgumentType::String
665            | &ArgumentType::Table
666            | &ArgumentType::Vararg => serializer.serialize_str(&self.to_string()),
667
668            ArgumentType::Constant(constants) => {
669                let mut seq = serializer.serialize_seq(Some(constants.len()))?;
670                for constant in constants {
671                    seq.serialize_element(constant)?;
672                }
673                seq.end()
674            }
675
676            ArgumentType::Display(display) => {
677                let mut map = serializer.serialize_map(Some(1))?;
678                map.serialize_entry("display", display)?;
679                map.end()
680            }
681        }
682    }
683}
684
685impl<'de> Deserialize<'de> for ArgumentType {
686    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
687        deserializer.deserialize_any(ArgumentTypeVisitor)
688    }
689}
690
691struct ArgumentTypeVisitor;
692
693impl<'de> Visitor<'de> for ArgumentTypeVisitor {
694    type Value = ArgumentType;
695
696    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
697        formatter.write_str("an argument type or an array of constant strings")
698    }
699
700    fn visit_map<A: de::MapAccess<'de>>(self, mut access: A) -> Result<Self::Value, A::Error> {
701        let mut map: HashMap<String, String> = HashMap::new();
702
703        while let Some((key, value)) = access.next_entry()? {
704            map.insert(key, value);
705        }
706
707        if let Some(display) = map.remove("display") {
708            Ok(ArgumentType::Display(display))
709        } else {
710            Err(de::Error::custom(
711                "map value must have a `display` property",
712            ))
713        }
714    }
715
716    fn visit_seq<A: de::SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
717        let mut constants = Vec::new();
718
719        while let Some(value) = seq.next_element()? {
720            constants.push(value);
721        }
722
723        Ok(ArgumentType::Constant(constants))
724    }
725
726    fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
727        match value {
728            "any" => Ok(ArgumentType::Any),
729            "bool" => Ok(ArgumentType::Bool),
730            "function" => Ok(ArgumentType::Function),
731            "nil" => Ok(ArgumentType::Nil),
732            "number" => Ok(ArgumentType::Number),
733            "string" => Ok(ArgumentType::String),
734            "table" => Ok(ArgumentType::Table),
735            "..." => Ok(ArgumentType::Vararg),
736            other => Err(de::Error::custom(format!("unknown type {other}"))),
737        }
738    }
739}
740
741impl fmt::Display for ArgumentType {
742    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
743        match self {
744            ArgumentType::Any => write!(formatter, "any"),
745            ArgumentType::Bool => write!(formatter, "bool"),
746            ArgumentType::Constant(options) => write!(
747                formatter,
748                "{}",
749                // TODO: This gets pretty ugly with a lot of variants
750                options
751                    .iter()
752                    .map(|string| format!("\"{string}\""))
753                    .collect::<Vec<_>>()
754                    .join(", ")
755            ),
756            ArgumentType::Display(display) => write!(formatter, "{display}"),
757            ArgumentType::Function => write!(formatter, "function"),
758            ArgumentType::Nil => write!(formatter, "nil"),
759            ArgumentType::Number => write!(formatter, "number"),
760            ArgumentType::String => write!(formatter, "string"),
761            ArgumentType::Table => write!(formatter, "table"),
762            ArgumentType::Vararg => write!(formatter, "..."),
763        }
764    }
765}
766
767#[derive(Clone, Debug, Hash, PartialEq, Eq)]
768pub enum Required {
769    NotRequired,
770    Required(Option<String>),
771}
772
773impl Default for Required {
774    fn default() -> Self {
775        Required::Required(None)
776    }
777}
778
779impl<'de> Deserialize<'de> for Required {
780    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
781        deserializer.deserialize_any(RequiredVisitor)
782    }
783}
784
785impl Serialize for Required {
786    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
787        match self {
788            Required::NotRequired => serializer.serialize_bool(false),
789            Required::Required(None) => serializer.serialize_bool(true),
790            Required::Required(Some(message)) => serializer.serialize_str(message),
791        }
792    }
793}
794
795struct RequiredVisitor;
796
797impl Visitor<'_> for RequiredVisitor {
798    type Value = Required;
799
800    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
801        formatter.write_str("a boolean or a string message (when required)")
802    }
803
804    fn visit_bool<E: de::Error>(self, value: bool) -> Result<Self::Value, E> {
805        if value {
806            Ok(Required::Required(None))
807        } else {
808            Ok(Required::NotRequired)
809        }
810    }
811
812    fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
813        Ok(Required::Required(Some(value.to_owned())))
814    }
815}
816
817#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
818#[serde(rename_all = "kebab-case")]
819pub enum Observes {
820    ReadWrite,
821    Read,
822    Write,
823}
824
825impl Default for Observes {
826    fn default() -> Self {
827        Self::ReadWrite
828    }
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}