glua_code_analysis/db_index/gmod_class/
mod.rs1use 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}