Skip to main content

glua_code_analysis/db_index/gmod_class/
mod.rs

1use std::collections::HashMap;
2
3use glua_parser::LuaSyntaxId;
4
5use super::LuaIndex;
6use crate::FileId;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum GmodScriptedClassCallKind {
10    DefineBaseClass,
11    AccessorFunc,
12    NetworkVar,
13    NetworkVarElement,
14    VguiRegister,
15    DermaDefineControl,
16}
17
18impl GmodScriptedClassCallKind {
19    pub fn from_call_name(call_name: &str) -> Option<Self> {
20        match call_name {
21            "DEFINE_BASECLASS" => Some(Self::DefineBaseClass),
22            "AccessorFunc" => Some(Self::AccessorFunc),
23            "NetworkVar" => Some(Self::NetworkVar),
24            "NetworkVarElement" => Some(Self::NetworkVarElement),
25            _ => None,
26        }
27    }
28
29    pub fn from_call_path(path: &str) -> Option<Self> {
30        if path == "vgui.Register" || path.ends_with(".vgui.Register") {
31            return Some(Self::VguiRegister);
32        }
33        if path == "derma.DefineControl" || path.ends_with(".derma.DefineControl") {
34            return Some(Self::DermaDefineControl);
35        }
36        None
37    }
38}
39
40#[derive(Debug, Clone, PartialEq)]
41pub enum GmodClassCallLiteral {
42    String(String),
43    Integer(i64),
44    Unsigned(u64),
45    Float(f64),
46    Boolean(bool),
47    Nil,
48    NameRef(String),
49}
50
51#[derive(Debug, Clone, PartialEq)]
52pub struct GmodClassCallArg {
53    pub syntax_id: LuaSyntaxId,
54    pub value: Option<GmodClassCallLiteral>,
55}
56
57#[derive(Debug, Clone, PartialEq)]
58pub struct GmodScriptedClassCallMetadata {
59    pub syntax_id: LuaSyntaxId,
60    pub literal_args: Vec<Option<GmodClassCallLiteral>>,
61    pub args: Vec<GmodClassCallArg>,
62}
63
64#[derive(Debug, Clone, Default, PartialEq)]
65pub struct GmodScriptedClassFileMetadata {
66    pub define_baseclass_calls: Vec<GmodScriptedClassCallMetadata>,
67    pub accessor_func_calls: Vec<GmodScriptedClassCallMetadata>,
68    pub network_var_calls: Vec<GmodScriptedClassCallMetadata>,
69    pub network_var_element_calls: Vec<GmodScriptedClassCallMetadata>,
70    pub vgui_register_calls: Vec<GmodScriptedClassCallMetadata>,
71    pub derma_define_control_calls: Vec<GmodScriptedClassCallMetadata>,
72}
73
74impl GmodScriptedClassFileMetadata {
75    pub fn get_define_baseclass_name(&self) -> Option<&str> {
76        self.define_baseclass_calls
77            .iter()
78            .rev()
79            .find_map(|call| match call.literal_args.first() {
80                Some(Some(GmodClassCallLiteral::String(name))) if !name.is_empty() => {
81                    Some(name.as_str())
82                }
83                _ => None,
84            })
85    }
86
87    fn calls_by_kind_mut(
88        &mut self,
89        kind: GmodScriptedClassCallKind,
90    ) -> &mut Vec<GmodScriptedClassCallMetadata> {
91        match kind {
92            GmodScriptedClassCallKind::DefineBaseClass => &mut self.define_baseclass_calls,
93            GmodScriptedClassCallKind::AccessorFunc => &mut self.accessor_func_calls,
94            GmodScriptedClassCallKind::NetworkVar => &mut self.network_var_calls,
95            GmodScriptedClassCallKind::NetworkVarElement => &mut self.network_var_element_calls,
96            GmodScriptedClassCallKind::VguiRegister => &mut self.vgui_register_calls,
97            GmodScriptedClassCallKind::DermaDefineControl => &mut self.derma_define_control_calls,
98        }
99    }
100}
101
102#[derive(Debug, Default)]
103pub struct GmodClassMetadataIndex {
104    file_metadata: HashMap<FileId, GmodScriptedClassFileMetadata>,
105    vgui_panels: HashMap<String, Option<String>>,
106}
107
108impl GmodClassMetadataIndex {
109    pub fn new() -> Self {
110        Self {
111            file_metadata: HashMap::new(),
112            vgui_panels: HashMap::new(),
113        }
114    }
115
116    fn extract_non_empty_string_literal(literal: &GmodClassCallLiteral) -> Option<String> {
117        match literal {
118            GmodClassCallLiteral::String(value) if !value.is_empty() => Some(value.clone()),
119            _ => None,
120        }
121    }
122
123    fn extract_non_empty_string_arg(
124        call_metadata: &GmodScriptedClassCallMetadata,
125        arg_index: usize,
126    ) -> Option<String> {
127        call_metadata
128            .args
129            .get(arg_index)
130            .and_then(|arg| arg.value.as_ref())
131            .and_then(Self::extract_non_empty_string_literal)
132    }
133
134    fn maybe_extract_vgui_panel(
135        kind: GmodScriptedClassCallKind,
136        call_metadata: &GmodScriptedClassCallMetadata,
137    ) -> Option<(String, Option<String>)> {
138        let base_arg_index = match kind {
139            GmodScriptedClassCallKind::VguiRegister => 2,
140            GmodScriptedClassCallKind::DermaDefineControl => 3,
141            _ => return None,
142        };
143
144        let panel_name = Self::extract_non_empty_string_arg(call_metadata, 0)?;
145        let base_name = Self::extract_non_empty_string_arg(call_metadata, base_arg_index);
146        Some((panel_name, base_name))
147    }
148
149    fn insert_vgui_panel_from_call(
150        vgui_panels: &mut HashMap<String, Option<String>>,
151        kind: GmodScriptedClassCallKind,
152        call_metadata: &GmodScriptedClassCallMetadata,
153    ) {
154        let Some((panel_name, base_name)) = Self::maybe_extract_vgui_panel(kind, call_metadata)
155        else {
156            return;
157        };
158
159        vgui_panels.insert(panel_name, base_name);
160    }
161
162    fn update_vgui_panels_from_call(
163        &mut self,
164        kind: GmodScriptedClassCallKind,
165        call_metadata: &GmodScriptedClassCallMetadata,
166    ) {
167        Self::insert_vgui_panel_from_call(&mut self.vgui_panels, kind, call_metadata);
168    }
169
170    fn recompute_vgui_panels(&mut self) {
171        let mut vgui_panels = HashMap::new();
172
173        for file_metadata in self.file_metadata.values() {
174            for call in &file_metadata.vgui_register_calls {
175                Self::insert_vgui_panel_from_call(
176                    &mut vgui_panels,
177                    GmodScriptedClassCallKind::VguiRegister,
178                    call,
179                );
180            }
181            for call in &file_metadata.derma_define_control_calls {
182                Self::insert_vgui_panel_from_call(
183                    &mut vgui_panels,
184                    GmodScriptedClassCallKind::DermaDefineControl,
185                    call,
186                );
187            }
188        }
189
190        self.vgui_panels = vgui_panels;
191    }
192
193    pub fn add_call(
194        &mut self,
195        file_id: FileId,
196        kind: GmodScriptedClassCallKind,
197        call_metadata: GmodScriptedClassCallMetadata,
198    ) {
199        self.update_vgui_panels_from_call(kind, &call_metadata);
200
201        self.file_metadata
202            .entry(file_id)
203            .or_default()
204            .calls_by_kind_mut(kind)
205            .push(call_metadata);
206    }
207
208    pub fn get_file_metadata(&self, file_id: &FileId) -> Option<&GmodScriptedClassFileMetadata> {
209        self.file_metadata.get(file_id)
210    }
211
212    pub fn get_define_baseclass_name(&self, file_id: &FileId) -> Option<&str> {
213        self.get_file_metadata(file_id)?.get_define_baseclass_name()
214    }
215
216    pub fn iter_file_metadata(
217        &self,
218    ) -> impl Iterator<Item = (&FileId, &GmodScriptedClassFileMetadata)> {
219        self.file_metadata.iter()
220    }
221
222    pub fn find_vgui_panel_definitions(
223        &self,
224        name: &str,
225    ) -> Vec<(FileId, &GmodScriptedClassCallMetadata)> {
226        if name.trim().is_empty() {
227            return Vec::new();
228        }
229
230        let mut definitions = Vec::new();
231        for (file_id, file_metadata) in &self.file_metadata {
232            for call in file_metadata
233                .vgui_register_calls
234                .iter()
235                .chain(file_metadata.derma_define_control_calls.iter())
236            {
237                let Some(Some(GmodClassCallLiteral::String(panel_name))) =
238                    call.literal_args.first()
239                else {
240                    continue;
241                };
242
243                if panel_name == name {
244                    definitions.push((*file_id, call));
245                }
246            }
247        }
248
249        definitions
250    }
251
252    pub fn get_vgui_panel_base(&self, name: &str) -> Option<Option<String>> {
253        self.vgui_panels.get(name).cloned()
254    }
255}
256
257impl LuaIndex for GmodClassMetadataIndex {
258    fn remove(&mut self, file_id: FileId) {
259        self.file_metadata.remove(&file_id);
260        self.recompute_vgui_panels();
261    }
262
263    fn clear(&mut self) {
264        self.file_metadata.clear();
265        self.recompute_vgui_panels();
266    }
267}