1mod module_definitions;
2
3use module_definitions::BuildModuleDefinitions;
4
5use std::collections::{HashMap, HashSet};
6use std::ops::{Deref, DerefMut};
7use std::path::{Path, PathBuf};
8use std::{iter, mem};
9
10use serde::Serialize;
11
12use crate::frontend::DarkluaResult;
13use crate::nodes::{
14 Block, DoStatement, Expression, FunctionCall, LocalAssignStatement, Prefix, Statement,
15 StringExpression,
16};
17use crate::process::{
18 to_expression, DefaultVisitor, IdentifierTracker, NodeProcessor, NodeVisitor, ScopeVisitor,
19};
20use crate::rules::require::{
21 is_require_call, match_path_require_call, PathRequireMode, RequirePathLocator,
22};
23use crate::rules::{
24 Context, ContextBuilder, FlawlessRule, ReplaceReferencedTokens, RuleProcessResult,
25};
26use crate::utils::Timer;
27use crate::{DarkluaError, Resources};
28
29use super::BundleOptions;
30
31pub(crate) enum RequiredResource {
32 Block(Block),
33 Expression(Expression),
34}
35
36#[derive(Debug)]
37struct RequirePathProcessor<'a, 'b, 'resources, 'code> {
38 options: &'a BundleOptions,
39 identifier_tracker: IdentifierTracker,
40 path_locator: RequirePathLocator<'b, 'code, 'resources>,
41 module_definitions: BuildModuleDefinitions,
42 source: PathBuf,
43 module_cache: HashMap<PathBuf, Expression>,
44 require_stack: Vec<PathBuf>,
45 skip_module_paths: HashSet<PathBuf>,
46 resources: &'resources Resources,
47 errors: Vec<String>,
48}
49
50impl<'a, 'b, 'code, 'resources> RequirePathProcessor<'a, 'b, 'code, 'resources> {
51 fn new<'context>(
52 context: &'context Context<'b, 'resources, 'code>,
53 options: &'a BundleOptions,
54 path_require_mode: &'b PathRequireMode,
55 ) -> Self
56 where
57 'context: 'b,
58 'context: 'resources,
59 'context: 'code,
60 {
61 Self {
62 options,
63 identifier_tracker: IdentifierTracker::new(),
64 path_locator: RequirePathLocator::new(
65 path_require_mode,
66 context.project_location(),
67 context.resources(),
68 ),
69 module_definitions: BuildModuleDefinitions::new(options.modules_identifier()),
70 source: context.current_path().to_path_buf(),
71 module_cache: Default::default(),
72 require_stack: Default::default(),
73 skip_module_paths: Default::default(),
74 resources: context.resources(),
75 errors: Vec::new(),
76 }
77 }
78
79 fn apply(self, block: &mut Block, context: &Context) -> RuleProcessResult {
80 self.module_definitions.apply(block, context);
81 match self.errors.len() {
82 0 => Ok(()),
83 1 => Err(self.errors.first().unwrap().to_string()),
84 _ => Err(format!("- {}", self.errors.join("\n- "))),
85 }
86 }
87
88 fn require_call(&self, call: &FunctionCall) -> Option<PathBuf> {
89 if is_require_call(call, self) {
90 match_path_require_call(call)
91 } else {
92 None
93 }
94 }
95
96 fn try_inline_call(&mut self, call: &FunctionCall) -> Option<Expression> {
97 let literal_require_path = self.require_call(call)?;
98
99 if self.options.is_excluded(&literal_require_path) {
100 log::info!(
101 "exclude `{}` from bundle [from `{}`]",
102 literal_require_path.display(),
103 self.source.display()
104 );
105 return None;
106 }
107
108 let require_path = match self
109 .path_locator
110 .find_require_path(&literal_require_path, &self.source)
111 {
112 Ok(path) => path,
113 Err(err) => {
114 self.errors.push(err.to_string());
115 return None;
116 }
117 };
118
119 log::debug!(
120 "found require call to path `{}` (normalized `{}`)",
121 literal_require_path.display(),
122 require_path.display()
123 );
124
125 if self.skip_module_paths.contains(&require_path) {
126 log::trace!(
127 "skip `{}` because it previously errored",
128 require_path.display()
129 );
130 return None;
131 }
132
133 match self.inline_require(&require_path, call) {
134 Ok(expression) => Some(expression),
135 Err(error) => {
136 self.errors.push(error.to_string());
137 self.skip_module_paths.insert(require_path);
138 None
139 }
140 }
141 }
142
143 fn inline_require(
144 &mut self,
145 require_path: &Path,
146 call: &FunctionCall,
147 ) -> DarkluaResult<Expression> {
148 if let Some(expression) = self.module_cache.get(require_path) {
149 Ok(expression.clone())
150 } else {
151 if let Some(i) = self
152 .require_stack
153 .iter()
154 .enumerate()
155 .find(|(_, path)| **path == require_path)
156 .map(|(i, _)| i)
157 {
158 let require_stack_paths: Vec<_> = self
159 .require_stack
160 .iter()
161 .skip(i)
162 .map(|path| path.display().to_string())
163 .chain(iter::once(require_path.display().to_string()))
164 .collect();
165
166 return Err(DarkluaError::custom(format!(
167 "cyclic require detected with `{}`",
168 require_stack_paths.join("` > `")
169 )));
170 }
171
172 self.require_stack.push(require_path.to_path_buf());
173 let required_resource = self.require_resource(require_path);
174 self.require_stack.pop();
175
176 let module_value = self.module_definitions.build_module_from_resource(
177 required_resource?,
178 require_path,
179 call,
180 )?;
181
182 self.module_cache
183 .insert(require_path.to_path_buf(), module_value.clone());
184
185 Ok(module_value)
186 }
187 }
188
189 fn require_resource(&mut self, path: impl AsRef<Path>) -> DarkluaResult<RequiredResource> {
190 let path = path.as_ref();
191 log::trace!("look for resource `{}`", path.display());
192 let content = self.resources.get(path).map_err(DarkluaError::from)?;
193
194 match path.extension() {
195 Some(extension) => match extension.to_string_lossy().as_ref() {
196 "lua" | "luau" => {
197 let parser_timer = Timer::now();
198 let mut block =
199 self.options
200 .parser()
201 .parse(&content)
202 .map_err(|parser_error| {
203 DarkluaError::parser_error(path.to_path_buf(), parser_error)
204 })?;
205 log::debug!(
206 "parsed `{}` in {}",
207 path.display(),
208 parser_timer.duration_label()
209 );
210
211 if self.options.parser().is_preserving_tokens() {
212 log::trace!("replacing token references of {}", path.display());
213 let context = ContextBuilder::new(path, self.resources, &content).build();
214 let replace_tokens = ReplaceReferencedTokens::default();
217
218 let apply_replace_tokens_timer = Timer::now();
219
220 replace_tokens.flawless_process(&mut block, &context);
221
222 log::trace!(
223 "replaced token references for `{}` in {}",
224 path.display(),
225 apply_replace_tokens_timer.duration_label()
226 );
227 }
228
229 let current_source = mem::replace(&mut self.source, path.to_path_buf());
230
231 let apply_processor_timer = Timer::now();
232 DefaultVisitor::visit_block(&mut block, self);
233
234 log::debug!(
235 "processed `{}` into bundle in {}",
236 path.display(),
237 apply_processor_timer.duration_label()
238 );
239
240 self.source = current_source;
241
242 Ok(RequiredResource::Block(block))
243 }
244 "json" | "json5" => {
245 transcode("json", path, json5::from_str::<serde_json::Value>, &content)
246 }
247 "yml" | "yaml" => transcode(
248 "yaml",
249 path,
250 serde_yaml::from_str::<serde_yaml::Value>,
251 &content,
252 ),
253 "toml" => transcode("toml", path, toml::from_str::<toml::Value>, &content),
254 "txt" => Ok(RequiredResource::Expression(
255 StringExpression::from_value(content).into(),
256 )),
257 _ => Err(DarkluaError::invalid_resource_extension(path)),
258 },
259 None => unreachable!("extension should be defined"),
260 }
261 }
262}
263
264impl<'a, 'b, 'resources, 'code> Deref for RequirePathProcessor<'a, 'b, 'resources, 'code> {
265 type Target = IdentifierTracker;
266
267 fn deref(&self) -> &Self::Target {
268 &self.identifier_tracker
269 }
270}
271
272impl<'a, 'b, 'resources, 'code> DerefMut for RequirePathProcessor<'a, 'b, 'resources, 'code> {
273 fn deref_mut(&mut self) -> &mut Self::Target {
274 &mut self.identifier_tracker
275 }
276}
277
278fn transcode<'a, T, E>(
279 label: &'static str,
280 path: &Path,
281 deserialize_value: impl Fn(&'a str) -> Result<T, E>,
282 content: &'a str,
283) -> Result<RequiredResource, DarkluaError>
284where
285 T: Serialize,
286 E: Into<DarkluaError>,
287{
288 log::trace!("transcode {} data to Lua from `{}`", label, path.display());
289 let transcode_duration = Timer::now();
290 let value = deserialize_value(content).map_err(E::into)?;
291 let expression = to_expression(&value)
292 .map(RequiredResource::Expression)
293 .map_err(DarkluaError::from);
294 log::debug!(
295 "transcoded {} data to Lua from `{}` in {}",
296 label,
297 path.display(),
298 transcode_duration.duration_label()
299 );
300 expression
301}
302
303impl<'a, 'b, 'resources, 'code> NodeProcessor for RequirePathProcessor<'a, 'b, 'resources, 'code> {
304 fn process_expression(&mut self, expression: &mut Expression) {
305 if let Expression::Call(call) = expression {
306 if let Some(replace_with) = self.try_inline_call(call) {
307 *expression = replace_with;
308 }
309 }
310 }
311
312 fn process_prefix_expression(&mut self, prefix: &mut Prefix) {
313 if let Prefix::Call(call) = prefix {
314 if let Some(replace_with) = self.try_inline_call(call) {
315 *prefix = replace_with.into();
316 }
317 }
318 }
319
320 fn process_statement(&mut self, statement: &mut Statement) {
321 if let Statement::Call(call) = statement {
322 if let Some(replace_with) = self.try_inline_call(call) {
323 if let Expression::Call(replace_with) = replace_with {
324 *call = *replace_with;
325 } else {
326 *statement = convert_expression_to_statement(replace_with);
327 }
328 }
329 }
330 }
331}
332
333fn convert_expression_to_statement(expression: Expression) -> Statement {
334 DoStatement::new(
335 Block::default()
336 .with_statement(LocalAssignStatement::from_variable("_").with_value(expression)),
337 )
338 .into()
339}
340
341pub(crate) fn process_block(
342 block: &mut Block,
343 context: &Context,
344 options: &BundleOptions,
345 path_require_mode: &PathRequireMode,
346) -> Result<(), String> {
347 if options.parser().is_preserving_tokens() {
348 log::trace!(
349 "replacing token references of {}",
350 context.current_path().display()
351 );
352 let replace_tokens = ReplaceReferencedTokens::default();
353
354 let apply_replace_tokens_timer = Timer::now();
355
356 replace_tokens.flawless_process(block, context);
357
358 log::trace!(
359 "replaced token references for `{}` in {}",
360 context.current_path().display(),
361 apply_replace_tokens_timer.duration_label()
362 );
363 }
364
365 let mut processor = RequirePathProcessor::new(context, options, path_require_mode);
366 ScopeVisitor::visit_block(block, &mut processor);
367 processor.apply(block, context)
368}