1use crate::error::{TemplateError, TemplateResult};
25use crate::context::TemplateContext;
26use crate::value::TemplateValue;
27use crate::utils::html_escape;
28use crate::bytecode::{CompiledTemplate, TemplateCompiler, BytecodeExecutor};
29use crate::layouts::LayoutProcessor;
30use crate::debug::{DebugInfo, DebugRenderResult, ExecutionStep};
31use crate::suggestions::{suggest_templates, extract_context_lines, find_line_column};
32use crate::lsp::{LspParseResult, TemplateBlock, CompletionItem, SyntaxToken, Diagnostic, HoverInfo, DefinitionInfo};
33use std::collections::HashMap;
34use std::fs;
35use std::path::Path;
36use std::sync::Arc;
37use std::thread;
38use std::time::SystemTime;
39
40#[derive(Debug, Clone)]
42pub struct MacroDefinition {
43 #[allow(dead_code)]
44 pub name: String,
45 pub parameters: Vec<String>,
46 pub body: String,
47}
48
49pub type HelperFunction = Arc<dyn Fn(&[TemplateValue]) -> TemplateResult<TemplateValue> + Send + Sync>;
51
52pub type FilterFunction = Arc<dyn Fn(&str, &[&str]) -> TemplateResult<String> + Send + Sync>;
54
55#[derive(Clone)]
88pub struct TemplateEngine {
89 template_dir: String,
90 cache: HashMap<String, String>,
91 bytecode_cache_enabled: bool,
92 bytecode_cache: HashMap<String, CompiledTemplate>,
93 compiler: TemplateCompiler,
94 executor: BytecodeExecutor,
95 layout_processor: LayoutProcessor,
96 macros: HashMap<String, MacroDefinition>,
97 helpers: HashMap<String, HelperFunction>,
98 translations: HashMap<String, HashMap<String, String>>, current_locale: Option<String>,
101 custom_filters: HashMap<String, FilterFunction>,
103
104 debug_enabled: bool,
107 hot_reload_enabled: bool,
109 file_mtimes: HashMap<String, SystemTime>,
111 template_dependencies: HashMap<String, Vec<String>>,
113
114 #[cfg(feature = "wasm")]
116 wasm_console_logging: bool,
118
119 performance_monitoring_enabled: bool,
122 compilation_stats: HashMap<String, (u64, std::time::Instant)>, render_stats: HashMap<String, Vec<u64>>, memory_usage_tracking: bool,
128}
129
130impl TemplateEngine {
131 pub fn new(template_dir: &str) -> Self {
156 Self {
157 template_dir: template_dir.to_string(),
158 cache: HashMap::new(),
159 bytecode_cache_enabled: false,
160 bytecode_cache: HashMap::new(),
161 compiler: TemplateCompiler::new(),
162 executor: BytecodeExecutor::new(),
163 layout_processor: LayoutProcessor::new(),
164 macros: HashMap::new(),
165 helpers: HashMap::new(),
166 translations: HashMap::new(),
167 current_locale: None,
168 custom_filters: HashMap::new(),
169 debug_enabled: false,
171 hot_reload_enabled: false,
172 file_mtimes: HashMap::new(),
173 template_dependencies: HashMap::new(),
174
175 #[cfg(feature = "wasm")]
177 wasm_console_logging: false,
178
179 performance_monitoring_enabled: false,
181 compilation_stats: HashMap::new(),
182 render_stats: HashMap::new(),
183 memory_usage_tracking: false,
184 }
185 }
186
187 pub fn register_helper<F>(&mut self, name: &str, func: F)
189 where
190 F: Fn(&[TemplateValue]) -> TemplateResult<TemplateValue> + Send + Sync + 'static,
191 {
192 self.helpers.insert(name.to_string(), Arc::new(func));
193 }
194
195 pub fn set_translations(&mut self, locale: &str, translations: HashMap<String, String>) {
197 self.translations.insert(locale.to_string(), translations);
198 }
199
200 pub fn set_locale(&mut self, locale: &str) {
202 self.current_locale = Some(locale.to_string());
203 }
204
205 pub fn get_translation(&self, key: &str) -> String {
207 if let Some(ref locale) = self.current_locale {
208 if let Some(translations) = self.translations.get(locale) {
209 if let Some(translation) = translations.get(key) {
210 return translation.clone();
211 }
212 }
213 }
214 key.to_string()
216 }
217
218 pub fn register_filter<F>(&mut self, name: &str, func: F)
220 where
221 F: Fn(&str, &[&str]) -> TemplateResult<String> + Send + Sync + 'static,
222 {
223 self.custom_filters.insert(name.to_string(), Arc::new(func));
224 }
225
226 pub fn load_template(&mut self, name: &str) -> TemplateResult<String> {
228 if let Some(cached) = self.cache.get(name) {
229 return Ok(cached.clone());
230 }
231
232 self.validate_template_path(name)?;
234
235 let path = Path::new(&self.template_dir).join(name);
236 let content = fs::read_to_string(&path)
237 .map_err(|e| TemplateError::Template(format!("Failed to read template '{}': {}", name, e)))?;
238
239 self.cache.insert(name.to_string(), content.clone());
240 Ok(content)
241 }
242
243 pub fn render(&mut self, template_name: &str, context: &TemplateContext) -> TemplateResult<String> {
245 let template = self.load_template(template_name)?;
246
247 self.layout_processor.parse_template(template_name, &template)?;
249
250 self.load_parent_templates(template_name)?;
252
253 let final_template = if self.has_layout_inheritance(template_name) {
255 self.layout_processor.resolve_inheritance(template_name)?
257 } else {
258 template
259 };
260
261 self.render_string(&final_template, context)
262 }
263
264 fn has_layout_inheritance(&self, template_name: &str) -> bool {
266 self.layout_processor.templates.get(template_name)
267 .map(|layout| layout.extends.is_some())
268 .unwrap_or(false)
269 }
270
271 fn load_parent_templates(&mut self, template_name: &str) -> TemplateResult<()> {
273 if let Some(layout) = self.layout_processor.templates.get(template_name).cloned() {
274 if let Some(parent_name) = layout.extends {
275 if !self.layout_processor.templates.contains_key(&parent_name) {
277 let parent_content = self.load_template(&parent_name)?;
278 self.layout_processor.parse_template(&parent_name, &parent_content)?;
279 }
280
281 self.load_parent_templates(&parent_name)?;
283 }
284 }
285 Ok(())
286 }
287
288 pub fn render_string(&mut self, template: &str, context: &TemplateContext) -> TemplateResult<String> {
290 let mut result = template.to_string();
291
292 result = self.process_macros_with_context(&result, context)?;
294
295 result = self.process_includes(&result)?;
297
298 result = self.process_conditionals(&result, context)?;
300
301 result = self.process_loops(&result, context)?;
303
304 result = self.process_translations(&result, context)?;
306
307 result = self.process_pluralization(&result, context)?;
309
310 result = self.process_variables(&result, context)?;
312
313 result = self.process_comments(&result);
315
316 Ok(result)
317 }
318
319 fn process_includes(&mut self, template: &str) -> TemplateResult<String> {
321 let mut result = template.to_string();
322
323 while let Some(start) = result.find("{{include ") {
324 let end = result[start..].find("}}")
325 .ok_or_else(|| TemplateError::Parse("Unclosed include directive".to_string()))?;
326
327 let directive = &result[start + 10..start + end];
328 let include_name = directive.trim().trim_matches('"').trim_matches('\'');
329
330 let included_content = self.load_template(include_name)?;
331
332 let processed_included_content = self.process_includes(&included_content)?;
334
335 result.replace_range(start..start + end + 2, &processed_included_content);
336 }
337
338 Ok(result)
339 }
340
341 fn process_conditionals(&self, template: &str, context: &TemplateContext) -> TemplateResult<String> {
343 let mut result = template.to_string();
344
345 while let Some(if_start) = result.find("{{if ") {
346 let if_end = result[if_start..].find("}}")
347 .ok_or_else(|| TemplateError::Parse("Unclosed if directive".to_string()))?;
348
349 let condition = &result[if_start + 5..if_start + if_end].trim();
350
351 let block_start = if_start + if_end + 2;
352 let block_end = result[block_start..].find("{{/if}}")
353 .ok_or_else(|| TemplateError::Parse("Missing {{/if}} directive".to_string()))?;
354
355 let block_content = result[block_start..block_start + block_end].to_string();
356
357 let should_include = self.evaluate_condition(condition, context);
358 let replacement = if should_include { &block_content } else { "" };
359
360 result.replace_range(if_start..block_start + block_end + 7, replacement);
361 }
362
363 Ok(result)
364 }
365
366 fn process_loops(&mut self, template: &str, context: &TemplateContext) -> TemplateResult<String> {
368 let mut result = template.to_string();
369
370 while let Some(for_start) = result.find("{{for ") {
371 let for_end = result[for_start..].find("}}")
372 .ok_or_else(|| TemplateError::Parse("Unclosed for directive".to_string()))?;
373
374 let loop_def = &result[for_start + 6..for_start + for_end].trim();
375 let parts: Vec<&str> = loop_def.split(" in ").collect();
376
377 if parts.len() != 2 {
378 return Err(TemplateError::Parse("Invalid for loop syntax".to_string()));
379 }
380
381 let item_var = parts[0].trim();
382 let array_var = parts[1].trim();
383
384 let block_start = for_start + for_end + 2;
385
386 let block_end = self.find_matching_for_end(&result[block_start..])?;
388
389 let block_content = &result[block_start..block_start + block_end];
390
391 let replacement = self.render_loop(item_var, array_var, block_content, context)?;
392
393 result.replace_range(for_start..block_start + block_end + 8, &replacement);
394 }
395
396 Ok(result)
397 }
398
399 fn process_variables(&self, template: &str, context: &TemplateContext) -> TemplateResult<String> {
401 let mut result = template.to_string();
402
403 while let Some(start) = result.find("{{& ") {
405 let end = result[start..].find("}}")
406 .ok_or_else(|| TemplateError::Parse("Unclosed variable directive".to_string()))?;
407
408 let var_name = &result[start + 4..start + end].trim();
409 let value = self.get_variable_value(var_name, context);
410
411 result.replace_range(start..start + end + 2, &value);
412 }
413
414 while let Some(start) = result.find("{{") {
416 if result[start..].starts_with("{{if ") ||
417 result[start..].starts_with("{{for ") ||
418 result[start..].starts_with("{{include ") ||
419 result[start..].starts_with("{{!") ||
420 result[start..].starts_with("{{/") {
421 if let Some(skip_end) = result[start..].find("}}") {
423 result = result[..start].to_string() + &result[start + skip_end + 2..];
424 continue;
425 } else {
426 break;
427 }
428 }
429
430 let end = result[start..].find("}}")
431 .ok_or_else(|| TemplateError::Parse("Unclosed variable directive".to_string()))?;
432
433 let var_name = &result[start + 2..start + end].trim();
434
435 if let Some(helper_result) = self.process_helper_call(var_name, context)? {
437 result.replace_range(start..start + end + 2, &helper_result);
438 continue;
439 }
440
441 let value = self.get_variable_value(var_name, context);
442
443 let should_escape = if var_name.contains('|') {
445 !self.uses_html_producing_filter(var_name)
446 } else {
447 true
448 };
449
450 let final_value = if should_escape {
451 html_escape(&value)
452 } else {
453 value
454 };
455
456 result.replace_range(start..start + end + 2, &final_value);
457 }
458
459 Ok(result)
460 }
461
462 fn process_helper_call(&self, expression: &str, context: &TemplateContext) -> TemplateResult<Option<String>> {
464 if let Some(paren_pos) = expression.find('(') {
466 let func_name = expression[..paren_pos].trim();
467
468 if let Some(helper) = self.helpers.get(func_name) {
470 if let Some(close_paren) = expression.rfind(')') {
471 let args_str = &expression[paren_pos + 1..close_paren];
472 let args = self.parse_helper_args(args_str, context)?;
473
474 let result_value = helper(&args)?;
476 let result_string = self.template_value_to_string(&result_value);
477 return Ok(Some(result_string));
478 } else {
479 return Err(TemplateError::Parse(format!("Unclosed parentheses in helper call: {}", expression)));
480 }
481 }
482 }
483
484 Ok(None)
485 }
486
487 fn parse_helper_args(&self, args_str: &str, context: &TemplateContext) -> TemplateResult<Vec<TemplateValue>> {
489 let mut args = Vec::new();
490
491 if args_str.trim().is_empty() {
492 return Ok(args);
493 }
494
495 let mut current_arg = String::new();
497 let mut in_quotes = false;
498 let mut quote_char = '"';
499
500 for ch in args_str.chars() {
501 match ch {
502 '"' | '\'' if !in_quotes => {
503 in_quotes = true;
504 quote_char = ch;
505 current_arg.push(ch);
506 },
507 ch if in_quotes && ch == quote_char => {
508 in_quotes = false;
509 current_arg.push(ch);
510 },
511 ',' if !in_quotes => {
512 let arg_value = self.parse_single_helper_arg(current_arg.trim(), context);
513 args.push(arg_value);
514 current_arg.clear();
515 },
516 ch => {
517 current_arg.push(ch);
518 }
519 }
520 }
521
522 if !current_arg.trim().is_empty() {
524 let arg_value = self.parse_single_helper_arg(current_arg.trim(), context);
525 args.push(arg_value);
526 }
527
528 Ok(args)
529 }
530
531 fn parse_single_helper_arg(&self, arg: &str, context: &TemplateContext) -> TemplateValue {
533 let arg = arg.trim();
534
535 if (arg.starts_with('"') && arg.ends_with('"')) || (arg.starts_with('\'') && arg.ends_with('\'')) {
537 return TemplateValue::String(arg[1..arg.len()-1].to_string());
538 }
539
540 if let Ok(num) = arg.parse::<i64>() {
542 return TemplateValue::Number(num);
543 }
544
545 if arg == "true" {
547 return TemplateValue::Bool(true);
548 } else if arg == "false" {
549 return TemplateValue::Bool(false);
550 }
551
552 if arg.contains('.') {
554 let parts: Vec<&str> = arg.split('.').collect();
555 if let Some(root_value) = context.variables.get(parts[0]) {
556 return self.get_nested_value(root_value, &parts[1..]);
557 }
558 } else if let Some(value) = context.variables.get(arg) {
559 return value.clone();
560 }
561
562 TemplateValue::String(arg.to_string())
564 }
565
566 #[allow(clippy::only_used_in_recursion)]
568 fn template_value_to_string(&self, value: &TemplateValue) -> String {
569 match value {
570 TemplateValue::String(s) => s.clone(),
571 TemplateValue::Number(n) => n.to_string(),
572 TemplateValue::Bool(b) => b.to_string(),
573 TemplateValue::Array(arr) => {
574 let items: Vec<String> = arr.iter().map(|v| self.template_value_to_string(v)).collect();
575 format!("[{}]", items.join(", "))
576 },
577 TemplateValue::Object(obj) => {
578 let pairs: Vec<String> = obj.iter()
579 .map(|(k, v)| format!("{}: {}", k, self.template_value_to_string(v)))
580 .collect();
581 format!("{{{}}}", pairs.join(", "))
582 }
583 }
584 }
585
586 fn get_variable_value(&self, var_name: &str, context: &TemplateContext) -> String {
588 if var_name.contains('|') {
590 return self.apply_filters(var_name, context);
591 }
592
593 if var_name.contains('.') {
594 let parts: Vec<&str> = var_name.split('.').collect();
595 if let Some(root_value) = context.variables.get(parts[0]) {
596 return self.traverse_nested_value(root_value, &parts[1..]);
597 }
598 String::new()
599 } else {
600 context.get_string(var_name).unwrap_or_default()
601 }
602 }
603
604 fn apply_filters(&self, expression: &str, context: &TemplateContext) -> String {
606 let parts: Vec<&str> = expression.split('|').collect();
607 if parts.is_empty() {
608 return String::new();
609 }
610
611 let var_name = parts[0].trim();
613 let mut value = if var_name.contains('.') {
614 let dot_parts: Vec<&str> = var_name.split('.').collect();
615 if let Some(root_value) = context.variables.get(dot_parts[0]) {
616 self.traverse_nested_value(root_value, &dot_parts[1..])
617 } else {
618 String::new()
619 }
620 } else {
621 context.get_string(var_name).unwrap_or_default()
622 };
623
624 for filter_expr in &parts[1..] {
626 value = self.apply_single_filter(&value, filter_expr.trim());
627 }
628
629 value
630 }
631
632 fn apply_single_filter(&self, value: &str, filter_expr: &str) -> String {
634 let filter_parts: Vec<&str> = filter_expr.split(':').collect();
635 let filter_name = filter_parts[0];
636 let args: Vec<&str> = if filter_parts.len() > 1 {
637 filter_parts[1..].iter().map(|arg| arg.trim_matches('"').trim_matches('\'')).collect()
638 } else {
639 Vec::new()
640 };
641
642 match filter_name {
643 "upper" => value.to_uppercase(),
644 "lower" => value.to_lowercase(),
645 "capitalize" => {
646 if value.is_empty() {
647 String::new()
648 } else {
649 value.split_whitespace()
650 .map(|word| {
651 let mut chars: Vec<char> = word.chars().collect();
652 if !chars.is_empty() {
653 chars[0] = chars[0].to_uppercase().next().unwrap_or(chars[0]);
654 }
655 chars.into_iter().collect::<String>()
656 })
657 .collect::<Vec<_>>()
658 .join(" ")
659 }
660 },
661 "truncate" => {
662 if let Some(limit_str) = args.first() {
663 if let Ok(limit) = limit_str.parse::<usize>() {
664 if value.len() > limit {
665 format!("{}...", &value[..limit.min(value.len())])
666 } else {
667 value.to_string()
668 }
669 } else {
670 value.to_string()
671 }
672 } else {
673 value.to_string()
674 }
675 },
676 "currency" => {
677 if let Ok(num) = value.parse::<i64>() {
678 if num >= 100 {
680 format!("${:.2}", num as f64 / 100.0)
681 } else {
682 format!("${:.2}", num as f64)
683 }
684 } else if let Ok(num) = value.parse::<f64>() {
685 format!("${:.2}", num)
686 } else {
687 format!("${}", value)
688 }
689 },
690 "date" => {
691 if let Some(format) = args.first() {
693 match *format {
695 "Y-m-d" => value.to_string(), _ => value.to_string(),
697 }
698 } else {
699 value.to_string()
700 }
701 },
702 "strip" => value.trim().to_string(),
703 "add" => {
704 if let Some(addend_str) = args.first() {
705 if let (Ok(num), Ok(addend)) = (value.parse::<i64>(), addend_str.parse::<i64>()) {
706 (num + addend).to_string()
707 } else {
708 value.to_string()
709 }
710 } else {
711 value.to_string()
712 }
713 },
714 "multiply" => {
715 if let Some(factor_str) = args.first() {
716 if let (Ok(num), Ok(factor)) = (value.parse::<i64>(), factor_str.parse::<i64>()) {
717 (num * factor).to_string()
718 } else {
719 value.to_string()
720 }
721 } else {
722 value.to_string()
723 }
724 },
725 "markdown" => {
727 let mut result = value.to_string();
729 while let Some(start) = result.find("**") {
730 if let Some(end) = result[start + 2..].find("**") {
731 let text = &result[start + 2..start + 2 + end];
732 let replacement = format!("<strong>{}</strong>", text);
733 result.replace_range(start..start + 2 + end + 2, &replacement);
734 } else {
735 break; }
737 }
738 format!("<p>{}</p>", result)
739 },
740 "highlight" => {
741 if let Some(lang) = args.first() {
742 format!("<pre><code class=\"{}\">{}</code></pre>", lang, value)
743 } else {
744 format!("<pre><code>{}</code></pre>", value)
745 }
746 },
747 "slugify" => {
748 value.to_lowercase()
749 .chars()
750 .map(|c| if c.is_alphanumeric() { c } else { '-' })
751 .collect::<String>()
752 .split('-')
753 .filter(|s| !s.is_empty())
754 .collect::<Vec<_>>()
755 .join("-")
756 },
757 "divide" => {
759 if let Some(arg) = args.first() {
760 if let Ok(num_value) = value.parse::<f64>() {
761 if let Ok(div_value) = arg.parse::<f64>() {
762 if div_value != 0.0 {
763 return (num_value / div_value).to_string();
764 }
765 }
766 }
767 }
768 value.to_string()
769 },
770 "percentage" => {
771 format!("{}%", value)
772 },
773 "round" => {
774 if let Some(arg) = args.first() {
775 if let Ok(num_value) = value.parse::<f64>() {
776 if let Ok(decimals) = arg.parse::<usize>() {
777 let factor = 10_f64.powi(decimals as i32);
778 let rounded = (num_value * factor).round() / factor;
779 return format!("{:.1$}", rounded, decimals);
780 }
781 }
782 }
783 if let Ok(num_value) = value.parse::<f64>() {
785 format!("{:.2}", num_value)
786 } else {
787 value.to_string()
788 }
789 },
790 _ => {
791 if let Some(custom_filter) = self.custom_filters.get(filter_name) {
793 match custom_filter(value, &args) {
794 Ok(result) => result,
795 Err(_) => value.to_string(), }
797 } else {
798 value.to_string() }
800 }
801 }
802 }
803
804 #[allow(clippy::only_used_in_recursion)]
806 fn traverse_nested_value(&self, current_value: &TemplateValue, remaining_parts: &[&str]) -> String {
807 if remaining_parts.is_empty() {
808 return match current_value {
810 TemplateValue::String(s) => s.clone(),
811 TemplateValue::Bool(b) => b.to_string(),
812 TemplateValue::Number(n) => n.to_string(),
813 TemplateValue::Array(_) => String::new(), TemplateValue::Object(_) => String::new(), };
816 }
817
818 let current_part = remaining_parts[0];
820 let next_parts = &remaining_parts[1..];
821
822 match current_value {
823 TemplateValue::Object(obj) => {
824 if let Some(next_value) = obj.get(current_part) {
825 self.traverse_nested_value(next_value, next_parts)
826 } else {
827 String::new() }
829 }
830 TemplateValue::Array(arr) => {
831 if let Ok(index) = current_part.parse::<usize>() {
833 if let Some(element) = arr.get(index) {
834 self.traverse_nested_value(element, next_parts)
835 } else {
836 String::new() }
838 } else {
839 String::new() }
841 }
842 _ => String::new(), }
844 }
845
846 fn process_comments(&self, template: &str) -> String {
848 let mut result = template.to_string();
849
850 while let Some(start) = result.find("{{!") {
851 if let Some(end) = result[start..].find("}}") {
852 result.replace_range(start..start + end + 2, "");
853 } else {
854 break;
855 }
856 }
857
858 result
859 }
860
861 fn process_macros_with_context(&mut self, template: &str, context: &TemplateContext) -> TemplateResult<String> {
863 let mut result = template.to_string();
864
865 result = self.extract_macro_definitions(&result)?;
867
868 result = self.process_macro_calls_with_context(&result, context)?;
870
871 Ok(result)
872 }
873
874 #[allow(dead_code)]
876 fn process_macros(&mut self, template: &str) -> TemplateResult<String> {
877 let mut result = template.to_string();
878
879 result = self.extract_macro_definitions(&result)?;
881
882 result = self.process_macro_calls(&result)?;
884
885 Ok(result)
886 }
887
888 fn extract_macro_definitions(&mut self, template: &str) -> TemplateResult<String> {
890 let mut result = template.to_string();
891
892 while let Some(macro_start) = result.find("{{macro ") {
893 let header_end = result[macro_start..].find("}}")
894 .ok_or_else(|| TemplateError::Parse("Unclosed macro definition".to_string()))?;
895
896 let macro_header = &result[macro_start + 8..macro_start + header_end];
897 let body_start = macro_start + header_end + 2;
898
899 let body_end = result[body_start..].find("{{/macro}}")
901 .ok_or_else(|| TemplateError::Parse("Missing {{/macro}} directive".to_string()))?;
902
903 let macro_body = result[body_start..body_start + body_end].trim().to_string();
904
905 let (macro_name, parameters) = self.parse_macro_header(macro_header)?;
907
908 self.macros.insert(macro_name.clone(), MacroDefinition {
910 name: macro_name,
911 parameters,
912 body: macro_body,
913 });
914
915 let macro_end = body_start + body_end + 10; result.replace_range(macro_start..macro_end, "");
918 }
919
920 Ok(result)
921 }
922
923 fn parse_macro_header(&self, header: &str) -> TemplateResult<(String, Vec<String>)> {
925 if let Some(paren_pos) = header.find('(') {
927 let macro_name = header[..paren_pos].trim().to_string();
928 let params_str = &header[paren_pos + 1..];
929
930 if let Some(close_paren) = params_str.rfind(')') {
931 let params_content = ¶ms_str[..close_paren];
932 let parameters = if params_content.trim().is_empty() {
933 Vec::new()
934 } else {
935 params_content.split(',')
936 .map(|p| {
937 let param = p.trim();
939 if let Some(eq_pos) = param.find('=') {
940 param[..eq_pos].trim().to_string()
941 } else {
942 param.to_string()
943 }
944 })
945 .collect()
946 };
947 Ok((macro_name, parameters))
948 } else {
949 Err(TemplateError::Parse(format!("Invalid macro header: {}", header)))
950 }
951 } else {
952 Ok((header.trim().to_string(), Vec::new()))
954 }
955 }
956
957 fn process_macro_calls_with_context(&mut self, template: &str, context: &TemplateContext) -> TemplateResult<String> {
959 let mut result = template.to_string();
960
961 let macros_clone = self.macros.clone();
963 for (macro_name, macro_def) in ¯os_clone {
964 let call_pattern = format!("{}{}", macro_name, "(");
965
966 while let Some(call_start) = result.find(&call_pattern) {
967 let start_pos = result[..call_start].rfind("{{")
969 .ok_or_else(|| TemplateError::Parse("Invalid macro call".to_string()))?;
970
971 let end_pos = result[call_start..].find("}}")
973 .ok_or_else(|| TemplateError::Parse("Unclosed macro call".to_string()))? + call_start + 2;
974
975 let call_content = &result[start_pos + 2..end_pos - 2];
976
977 if !self.can_resolve_macro_args(call_content, context)? {
979 break;
981 }
982
983 let args = self.parse_macro_call_args_with_context(call_content, context)?;
985
986 let expanded = self.expand_macro_with_values(macro_def, &args)?;
988
989 result.replace_range(start_pos..end_pos, &expanded);
990 }
991 }
992
993 Ok(result)
994 }
995
996 #[allow(dead_code)]
998 fn process_macro_calls(&mut self, template: &str) -> TemplateResult<String> {
999 let empty_context = TemplateContext::new();
1000 self.process_macro_calls_with_context(template, &empty_context)
1001 }
1002
1003 #[allow(dead_code)]
1005 fn parse_macro_call_args(&self, call_content: &str) -> TemplateResult<HashMap<String, String>> {
1006 if let Some(paren_start) = call_content.find('(') {
1008 if let Some(paren_end) = call_content.rfind(')') {
1009 let args_str = &call_content[paren_start + 1..paren_end];
1010 let mut args_map = HashMap::new();
1011 let mut positional_index = 0;
1012
1013 if !args_str.trim().is_empty() {
1014 let args = self.parse_argument_list(args_str)?;
1016
1017 for arg in args {
1018 if let Some(eq_pos) = arg.find('=') {
1019 let param_name = arg[..eq_pos].trim().to_string();
1021 let param_value = arg[eq_pos + 1..].trim().trim_matches('"').trim_matches('\'').to_string();
1022 args_map.insert(param_name, param_value);
1023 } else {
1024 let param_value = arg.trim().trim_matches('"').trim_matches('\'').to_string();
1026 args_map.insert(positional_index.to_string(), param_value);
1027 positional_index += 1;
1028 }
1029 }
1030 }
1031 Ok(args_map)
1032 } else {
1033 Err(TemplateError::Parse("Invalid macro call syntax".to_string()))
1034 }
1035 } else {
1036 Err(TemplateError::Parse("Invalid macro call syntax".to_string()))
1037 }
1038 }
1039
1040 fn parse_argument_list(&self, args_str: &str) -> TemplateResult<Vec<String>> {
1042 let mut args = Vec::new();
1043 let mut current_arg = String::new();
1044 let mut in_quotes = false;
1045 let mut quote_char = '"';
1046
1047 for ch in args_str.chars() {
1048 match ch {
1049 '"' | '\'' if !in_quotes => {
1050 in_quotes = true;
1051 quote_char = ch;
1052 current_arg.push(ch);
1053 },
1054 ch if in_quotes && ch == quote_char => {
1055 in_quotes = false;
1056 current_arg.push(ch);
1057 },
1058 ',' if !in_quotes => {
1059 args.push(current_arg.trim().to_string());
1060 current_arg.clear();
1061 },
1062 ch => {
1063 current_arg.push(ch);
1064 }
1065 }
1066 }
1067
1068 if !current_arg.trim().is_empty() {
1069 args.push(current_arg.trim().to_string());
1070 }
1071
1072 Ok(args)
1073 }
1074
1075 fn can_resolve_macro_args(&self, call_content: &str, context: &TemplateContext) -> TemplateResult<bool> {
1077 if let Some(paren_start) = call_content.find('(') {
1079 if let Some(paren_end) = call_content.rfind(')') {
1080 let args_str = &call_content[paren_start + 1..paren_end];
1081
1082 if !args_str.trim().is_empty() {
1083 let args = self.parse_argument_list(args_str)?;
1085
1086 for arg in args {
1087 if let Some(eq_pos) = arg.find('=') {
1088 let param_value_str = arg[eq_pos + 1..].trim();
1090 if !param_value_str.starts_with('"') && !param_value_str.starts_with('\'') {
1091 if !self.variable_exists_in_context(param_value_str, context) {
1093 return Ok(false);
1094 }
1095 }
1096 } else {
1097 let arg_trimmed = arg.trim();
1099 if !arg_trimmed.starts_with('"') && !arg_trimmed.starts_with('\'') {
1100 if !self.variable_exists_in_context(arg_trimmed, context) {
1102 return Ok(false);
1103 }
1104 }
1105 }
1106 }
1107 }
1108 Ok(true)
1109 } else {
1110 Err(TemplateError::Parse("Invalid macro call syntax".to_string()))
1111 }
1112 } else {
1113 Err(TemplateError::Parse("Invalid macro call syntax".to_string()))
1114 }
1115 }
1116
1117 fn variable_exists_in_context(&self, variable_name: &str, context: &TemplateContext) -> bool {
1119 if variable_name.contains('.') {
1120 let parts: Vec<&str> = variable_name.split('.').collect();
1121 context.get(parts[0]).is_some()
1122 } else {
1123 context.get(variable_name).is_some()
1124 }
1125 }
1126
1127 fn parse_macro_call_args_with_context(&self, call_content: &str, context: &TemplateContext) -> TemplateResult<HashMap<String, TemplateValue>> {
1129 if let Some(paren_start) = call_content.find('(') {
1131 if let Some(paren_end) = call_content.rfind(')') {
1132 let args_str = &call_content[paren_start + 1..paren_end];
1133 let mut args_map = HashMap::new();
1134 let mut positional_index = 0;
1135
1136 if !args_str.trim().is_empty() {
1137 let args = self.parse_argument_list(args_str)?;
1139
1140 for arg in args {
1141 if let Some(eq_pos) = arg.find('=') {
1142 let param_name = arg[..eq_pos].trim().to_string();
1144 let param_value_str = arg[eq_pos + 1..].trim();
1145 let param_value = if param_value_str.starts_with('"') || param_value_str.starts_with('\'') {
1146 TemplateValue::String(param_value_str.trim_matches('"').trim_matches('\'').to_string())
1148 } else {
1149 self.resolve_variable_from_context(param_value_str, context)
1151 };
1152 args_map.insert(param_name, param_value);
1153 } else {
1154 let arg_trimmed = arg.trim();
1156 let param_value = if arg_trimmed.starts_with('"') || arg_trimmed.starts_with('\'') {
1157 TemplateValue::String(arg_trimmed.trim_matches('"').trim_matches('\'').to_string())
1159 } else {
1160 self.resolve_variable_from_context(arg_trimmed, context)
1162 };
1163 args_map.insert(positional_index.to_string(), param_value);
1164 positional_index += 1;
1165 }
1166 }
1167 }
1168 Ok(args_map)
1169 } else {
1170 Err(TemplateError::Parse("Invalid macro call syntax".to_string()))
1171 }
1172 } else {
1173 Err(TemplateError::Parse("Invalid macro call syntax".to_string()))
1174 }
1175 }
1176
1177 fn resolve_variable_from_context(&self, variable_name: &str, context: &TemplateContext) -> TemplateValue {
1179 if variable_name.contains('.') {
1180 let parts: Vec<&str> = variable_name.split('.').collect();
1182 if let Some(root_value) = context.get(parts[0]) {
1183 self.get_nested_value(root_value, &parts[1..])
1184 } else {
1185 TemplateValue::String(String::new())
1186 }
1187 } else {
1188 context.get(variable_name).unwrap_or(&TemplateValue::String(String::new())).clone()
1190 }
1191 }
1192
1193 #[allow(dead_code)]
1195 fn expand_macro(&mut self, macro_def: &MacroDefinition, args: &HashMap<String, String>) -> TemplateResult<String> {
1196 let macro_body = macro_def.body.clone();
1197
1198 let mut macro_context = TemplateContext::new();
1200
1201 for (i, param) in macro_def.parameters.iter().enumerate() {
1203 let value = if let Some(named_value) = args.get(param) {
1204 TemplateValue::String(named_value.clone())
1206 } else if let Some(positional_value) = args.get(&i.to_string()) {
1207 TemplateValue::String(positional_value.clone())
1209 } else {
1210 TemplateValue::String(String::new())
1211 };
1212
1213 macro_context.set(param, value);
1214 }
1215
1216 self.render_string(¯o_body, ¯o_context)
1218 }
1219
1220 fn expand_macro_with_values(&mut self, macro_def: &MacroDefinition, args: &HashMap<String, TemplateValue>) -> TemplateResult<String> {
1222 let macro_body = macro_def.body.clone();
1223
1224 let mut macro_context = TemplateContext::new();
1226
1227 for (i, param) in macro_def.parameters.iter().enumerate() {
1229 let value = if let Some(named_value) = args.get(param) {
1230 named_value.clone()
1231 } else if let Some(positional_value) = args.get(&i.to_string()) {
1232 positional_value.clone()
1233 } else {
1234 TemplateValue::String(String::new())
1235 };
1236
1237 macro_context.set(param, value);
1238 }
1239
1240 self.render_string(¯o_body, ¯o_context)
1242 }
1243
1244 fn uses_html_producing_filter(&self, var_expression: &str) -> bool {
1246 let html_filters = ["markdown", "highlight"];
1247
1248 if let Some(_filter_part) = var_expression.split('|').nth(1) {
1249 let filters: Vec<&str> = var_expression.split('|').skip(1).collect();
1250 for filter_expr in filters {
1251 let filter_name = filter_expr.split(':').next().unwrap_or("").trim();
1252 if html_filters.contains(&filter_name) {
1253 return true;
1254 }
1255 }
1256 }
1257
1258 false
1259 }
1260
1261 fn evaluate_condition(&self, condition: &str, context: &TemplateContext) -> bool {
1263 let condition = condition.trim();
1264
1265 if let Some(result) = self.evaluate_comparison(condition, context) {
1267 return result;
1268 }
1269
1270 if condition.contains('.') {
1272 let parts: Vec<&str> = condition.split('.').collect();
1273 if let Some(root_value) = context.variables.get(parts[0]) {
1274 return self.evaluate_nested_condition(root_value, &parts[1..]);
1275 }
1276 false
1277 } else if let Some(value) = context.variables.get(condition) {
1278 self.is_truthy(value)
1279 } else {
1280 false
1281 }
1282 }
1283
1284 fn evaluate_comparison(&self, condition: &str, context: &TemplateContext) -> Option<bool> {
1286 let operators = ["==", "!=", "<=", ">=", "<", ">"];
1288
1289 for op in &operators {
1290 if let Some(op_pos) = condition.find(op) {
1291 let left_expr = condition[..op_pos].trim();
1292 let right_expr = condition[op_pos + op.len()..].trim();
1293
1294 let left_val = self.get_condition_value(left_expr, context);
1295 let right_val = self.get_condition_value(right_expr, context);
1296
1297 return Some(match *op {
1298 "==" => self.values_equal(&left_val, &right_val),
1299 "!=" => !self.values_equal(&left_val, &right_val),
1300 "<" => self.compare_values(&left_val, &right_val) < 0,
1301 ">" => self.compare_values(&left_val, &right_val) > 0,
1302 "<=" => self.compare_values(&left_val, &right_val) <= 0,
1303 ">=" => self.compare_values(&left_val, &right_val) >= 0,
1304 _ => false,
1305 });
1306 }
1307 }
1308
1309 None
1310 }
1311
1312 fn get_condition_value(&self, expr: &str, context: &TemplateContext) -> TemplateValue {
1314 let expr = expr.trim();
1315
1316 if (expr.starts_with('"') && expr.ends_with('"')) || (expr.starts_with('\'') && expr.ends_with('\'')) {
1318 return TemplateValue::String(expr[1..expr.len()-1].to_string());
1319 }
1320
1321 if let Ok(num) = expr.parse::<i64>() {
1323 return TemplateValue::Number(num);
1324 }
1325
1326 if expr == "true" {
1328 return TemplateValue::Bool(true);
1329 } else if expr == "false" {
1330 return TemplateValue::Bool(false);
1331 }
1332
1333 if expr.contains('.') {
1335 let parts: Vec<&str> = expr.split('.').collect();
1336 if let Some(root_value) = context.variables.get(parts[0]) {
1337 return self.get_nested_value(root_value, &parts[1..]);
1338 }
1339 } else if let Some(value) = context.variables.get(expr) {
1340 return value.clone();
1341 }
1342
1343 TemplateValue::String(String::new())
1345 }
1346
1347 #[allow(clippy::only_used_in_recursion)]
1349 fn get_nested_value(&self, current_value: &TemplateValue, remaining_parts: &[&str]) -> TemplateValue {
1350 if remaining_parts.is_empty() {
1351 return current_value.clone();
1352 }
1353
1354 let current_part = remaining_parts[0];
1355 let next_parts = &remaining_parts[1..];
1356
1357 match current_value {
1358 TemplateValue::Object(obj) => {
1359 if let Some(next_value) = obj.get(current_part) {
1360 self.get_nested_value(next_value, next_parts)
1361 } else {
1362 TemplateValue::String(String::new())
1363 }
1364 }
1365 TemplateValue::Array(arr) => {
1366 if let Ok(index) = current_part.parse::<usize>() {
1367 if let Some(element) = arr.get(index) {
1368 self.get_nested_value(element, next_parts)
1369 } else {
1370 TemplateValue::String(String::new())
1371 }
1372 } else {
1373 TemplateValue::String(String::new())
1374 }
1375 }
1376 _ => TemplateValue::String(String::new()),
1377 }
1378 }
1379
1380 fn values_equal(&self, left: &TemplateValue, right: &TemplateValue) -> bool {
1382 match (left, right) {
1383 (TemplateValue::String(a), TemplateValue::String(b)) => a == b,
1384 (TemplateValue::Number(a), TemplateValue::Number(b)) => a == b,
1385 (TemplateValue::Bool(a), TemplateValue::Bool(b)) => a == b,
1386 (TemplateValue::Array(a), TemplateValue::Array(b)) => a.len() == b.len(),
1387 (TemplateValue::Object(a), TemplateValue::Object(b)) => a.len() == b.len(),
1388 _ => self.value_to_string(left) == self.value_to_string(right),
1390 }
1391 }
1392
1393 fn compare_values(&self, left: &TemplateValue, right: &TemplateValue) -> i32 {
1395 match (left, right) {
1396 (TemplateValue::Number(a), TemplateValue::Number(b)) => {
1397 a.cmp(b) as i32
1398 }
1399 (TemplateValue::String(a), TemplateValue::String(b)) => {
1400 a.cmp(b) as i32
1401 }
1402 _ => {
1404 let a_str = self.value_to_string(left);
1405 let b_str = self.value_to_string(right);
1406 a_str.cmp(&b_str) as i32
1407 }
1408 }
1409 }
1410
1411 fn value_to_string(&self, value: &TemplateValue) -> String {
1413 match value {
1414 TemplateValue::String(s) => s.clone(),
1415 TemplateValue::Number(n) => n.to_string(),
1416 TemplateValue::Bool(b) => b.to_string(),
1417 TemplateValue::Array(_) => "[Array]".to_string(),
1418 TemplateValue::Object(_) => "[Object]".to_string(),
1419 }
1420 }
1421
1422 fn evaluate_nested_condition(&self, current_value: &TemplateValue, remaining_parts: &[&str]) -> bool {
1424 if remaining_parts.is_empty() {
1425 return self.is_truthy(current_value);
1426 }
1427
1428 let current_part = remaining_parts[0];
1429 let next_parts = &remaining_parts[1..];
1430
1431 match current_value {
1432 TemplateValue::Object(obj) => {
1433 if let Some(next_value) = obj.get(current_part) {
1434 self.evaluate_nested_condition(next_value, next_parts)
1435 } else {
1436 false }
1438 }
1439 TemplateValue::Array(arr) => {
1440 if let Ok(index) = current_part.parse::<usize>() {
1442 if let Some(element) = arr.get(index) {
1443 self.evaluate_nested_condition(element, next_parts)
1444 } else {
1445 false }
1447 } else {
1448 false }
1450 }
1451 _ => false, }
1453 }
1454
1455 fn is_truthy(&self, value: &TemplateValue) -> bool {
1457 match value {
1458 TemplateValue::Bool(b) => *b,
1459 TemplateValue::String(s) => !s.is_empty(),
1460 TemplateValue::Number(n) => *n != 0,
1461 TemplateValue::Array(a) => !a.is_empty(),
1462 TemplateValue::Object(o) => !o.is_empty(),
1463 }
1464 }
1465
1466 fn render_loop(&mut self, item_var: &str, array_var: &str, block: &str, context: &TemplateContext) -> TemplateResult<String> {
1468 if let Some(TemplateValue::Array(items)) = context.variables.get(array_var) {
1469 let mut result = String::new();
1470
1471 for item in items {
1472 let mut loop_context = context.clone();
1473 loop_context.set(item_var, item.clone());
1474
1475 let mut processed_block = self.process_macro_calls_with_context(block, &loop_context)?;
1477
1478 processed_block = self.process_loops(&processed_block, &loop_context)?;
1480
1481 processed_block = self.process_conditionals(&processed_block, &loop_context)?;
1483
1484 processed_block = self.process_variables(&processed_block, &loop_context)?;
1486
1487 result.push_str(&processed_block);
1488 }
1489
1490 Ok(result)
1491 } else {
1492 if array_var.contains('(') && array_var.contains(')') {
1494 return Err(TemplateError::Template(format!("Function '{}' is not supported", array_var)));
1495 }
1496 Ok(String::new())
1498 }
1499 }
1500
1501 fn validate_template_path(&self, name: &str) -> TemplateResult<()> {
1503 if name.contains("..") {
1505 return Err(TemplateError::Security("Path traversal attempt detected".to_string()));
1506 }
1507
1508 if name.starts_with('/') || name.starts_with('\\') {
1510 return Err(TemplateError::Security("Absolute path not allowed".to_string()));
1511 }
1512
1513 if name.len() >= 3 && name.chars().nth(1) == Some(':') {
1515 return Err(TemplateError::Security("Drive letter path not allowed".to_string()));
1516 }
1517
1518 let template_dir = Path::new(&self.template_dir).canonicalize()
1520 .map_err(|_| TemplateError::Security("Invalid template directory".to_string()))?;
1521
1522 let requested_path = template_dir.join(name).canonicalize();
1523
1524 match requested_path {
1525 Ok(resolved_path) => {
1526 if !resolved_path.starts_with(&template_dir) {
1527 return Err(TemplateError::Security("Path traversal attempt detected".to_string()));
1528 }
1529 Ok(())
1530 }
1531 Err(_) => {
1532 Ok(())
1535 }
1536 }
1537 }
1538
1539 fn find_matching_for_end(&self, content: &str) -> TemplateResult<usize> {
1541 let mut depth = 1; let mut pos = 0;
1543
1544 while pos < content.len() && depth > 0 {
1545 if let Some(for_pos) = content[pos..].find("{{for ") {
1546 let actual_for_pos = pos + for_pos;
1547
1548 if let Some(end_for_pos) = content[pos..pos + for_pos].find("{{/for}}") {
1550 let actual_end_for_pos = pos + end_for_pos;
1551 depth -= 1;
1552 if depth == 0 {
1553 return Ok(actual_end_for_pos);
1554 }
1555 pos = actual_end_for_pos + 8; continue;
1557 }
1558
1559 depth += 1;
1561 pos = actual_for_pos + 6; } else if let Some(end_for_pos) = content[pos..].find("{{/for}}") {
1563 let actual_end_for_pos = pos + end_for_pos;
1564 depth -= 1;
1565 if depth == 0 {
1566 return Ok(actual_end_for_pos);
1567 }
1568 pos = actual_end_for_pos + 8; } else {
1570 break; }
1572 }
1573
1574 Err(TemplateError::Parse("Missing {{/for}} directive".to_string()))
1575 }
1576
1577
1578 pub fn render_parallel(&mut self, template_names: &[String], context: &TemplateContext) -> TemplateResult<Vec<String>> {
1582 let context = Arc::new(context.clone());
1583 let template_dir = Arc::new(self.template_dir.clone());
1584
1585 let handles: Vec<_> = template_names.iter().map(|name| {
1586 let name = name.clone();
1587 let context = Arc::clone(&context);
1588 let template_dir = Arc::clone(&template_dir);
1589
1590 thread::spawn(move || {
1591 let mut engine = TemplateEngine::new(&template_dir);
1592 engine.render(&name, &context)
1593 })
1594 }).collect();
1595
1596 let mut results = Vec::new();
1597 for handle in handles {
1598 let result = handle.join().map_err(|_| TemplateError::Render("Thread panic".to_string()))??;
1599 results.push(result);
1600 }
1601
1602 Ok(results)
1603 }
1604
1605 pub fn load_template_mmap(&mut self, name: &str) -> TemplateResult<String> {
1608 if let Some(cached) = self.cache.get(name) {
1610 return Ok(cached.clone());
1611 }
1612
1613 self.validate_template_path(name)?;
1615
1616 let path = Path::new(&self.template_dir).join(name);
1617 let content = fs::read_to_string(&path)
1618 .map_err(|e| TemplateError::Template(format!("Failed to mmap template '{}': {}", name, e)))?;
1619
1620 self.cache.insert(name.to_string(), content.clone());
1626 Ok(content)
1627 }
1628
1629 pub fn compile_to_bytecode(&mut self, template_name: &str) -> TemplateResult<CompiledTemplate> {
1631 if self.bytecode_cache_enabled {
1632 if let Some(cached) = self.bytecode_cache.get(template_name) {
1633 return Ok(cached.clone());
1634 }
1635 }
1636
1637 let template_content = self.load_template(template_name)?;
1638 let instructions = self.compiler.compile(&template_content)?;
1639 let compiled = CompiledTemplate::new(template_name.to_string(), instructions);
1640
1641 if self.bytecode_cache_enabled {
1642 self.bytecode_cache.insert(template_name.to_string(), compiled.clone());
1643 }
1644
1645 Ok(compiled)
1646 }
1647
1648 pub fn compile_to_bytecode_uncached(&mut self, template_name: &str) -> TemplateResult<CompiledTemplate> {
1650 let template_content = self.load_template(template_name)?;
1651 let instructions = self.compiler.compile(&template_content)?;
1652 Ok(CompiledTemplate::new(template_name.to_string(), instructions))
1653 }
1654
1655 pub fn render_compiled(&self, compiled_template: &CompiledTemplate, context: &TemplateContext) -> TemplateResult<String> {
1657 self.executor.execute(&compiled_template.instructions, context)
1658 }
1659
1660 pub fn is_bytecode_cached(&self, template_name: &str) -> bool {
1662 self.bytecode_cache.contains_key(template_name)
1663 }
1664
1665 pub fn enable_bytecode_cache(&mut self, enabled: bool) {
1667 self.bytecode_cache_enabled = enabled;
1668 if !enabled {
1669 self.bytecode_cache.clear();
1670 }
1671 }
1672
1673 pub fn compile_templates_parallel(&mut self, template_names: &[String]) -> TemplateResult<Vec<CompiledTemplate>> {
1675 let template_dir = Arc::new(self.template_dir.clone());
1676
1677 let handles: Vec<_> = template_names.iter().map(|name| {
1678 let name = name.clone();
1679 let template_dir = Arc::clone(&template_dir);
1680
1681 thread::spawn(move || {
1682 let mut engine = TemplateEngine::new(&template_dir);
1683 engine.compile_to_bytecode(&name)
1684 })
1685 }).collect();
1686
1687 let mut results = Vec::new();
1688 for handle in handles {
1689 let result = handle.join().map_err(|_| TemplateError::Render("Thread panic".to_string()))??;
1690 results.push(result);
1691 }
1692
1693 Ok(results)
1694 }
1695
1696 pub fn render_compiled_parallel(&self, compiled_templates: &[CompiledTemplate], context: &TemplateContext) -> TemplateResult<Vec<String>> {
1698 let context = Arc::new(context.clone());
1699 let executor = Arc::new(self.executor.clone());
1700
1701 let handles: Vec<_> = compiled_templates.iter().map(|template| {
1702 let template = template.clone();
1703 let context = Arc::clone(&context);
1704 let executor: Arc<BytecodeExecutor> = Arc::clone(&executor);
1705
1706 thread::spawn(move || {
1707 executor.execute(&template.instructions, &context)
1708 })
1709 }).collect();
1710
1711 let mut results = Vec::new();
1712 for handle in handles {
1713 let result = handle.join().map_err(|_| TemplateError::Render("Thread panic".to_string()))??;
1714 results.push(result);
1715 }
1716
1717 Ok(results)
1718 }
1719
1720 fn process_translations(&mut self, template: &str, context: &TemplateContext) -> TemplateResult<String> {
1722 let mut result = template.to_string();
1723
1724 while let Some(start) = result.find("{{t ") {
1725 let end = result[start..].find("}}")
1726 .ok_or_else(|| TemplateError::Parse("Unclosed translation directive".to_string()))?;
1727
1728 let directive = &result[start + 4..start + end];
1729 let translation_key = directive.trim().trim_matches('"').trim_matches('\'');
1730
1731 let translation = self.get_translation(translation_key);
1732
1733 let processed_translation = self.render_string(&translation, context)?;
1735
1736 result.replace_range(start..start + end + 2, &processed_translation);
1737 }
1738
1739 Ok(result)
1740 }
1741
1742 fn process_pluralization(&self, template: &str, context: &TemplateContext) -> TemplateResult<String> {
1744 let mut result = template.to_string();
1745
1746 while let Some(start) = result.find("{{plural ") {
1747 let end = result[start..].find("}}")
1748 .ok_or_else(|| TemplateError::Parse("Unclosed pluralization directive".to_string()))?;
1749
1750 let directive = result[start + 9..start + end].to_string();
1751 let parts: Vec<&str> = directive.split_whitespace().collect();
1752
1753 if parts.len() != 3 {
1754 return Err(TemplateError::Parse("Invalid pluralization syntax. Use: {{plural count \"singular\" \"plural\"}}".to_string()));
1755 }
1756
1757 let count_var = parts[0];
1758 let singular = parts[1].trim_matches('"').trim_matches('\'');
1759 let plural = parts[2].trim_matches('"').trim_matches('\'');
1760
1761 let count = if let Some(TemplateValue::Number(n)) = context.get(count_var) {
1763 *n
1764 } else {
1765 0
1766 };
1767
1768 let chosen_form = if count == 1 { singular } else { plural };
1769
1770 result.replace_range(start..start + end + 2, chosen_form);
1771 }
1772
1773 Ok(result)
1774 }
1775
1776 pub fn enable_debug_mode(&mut self) {
1782 self.debug_enabled = true;
1783 }
1784
1785 pub fn disable_debug_mode(&mut self) {
1787 self.debug_enabled = false;
1788 }
1789
1790 pub fn is_debug_enabled(&self) -> bool {
1792 self.debug_enabled
1793 }
1794
1795 pub fn get_template_dir(&self) -> &str {
1799 &self.template_dir
1800 }
1801
1802 #[cfg(feature = "wasm")]
1804 pub fn get_wasm_console_logging(&self) -> bool {
1805 self.wasm_console_logging
1806 }
1807
1808 #[cfg(feature = "wasm")]
1810 pub fn set_wasm_console_logging(&mut self, enabled: bool) {
1811 self.wasm_console_logging = enabled;
1812 }
1813
1814 #[cfg(feature = "wasm")]
1816 pub fn get_cache_size(&self) -> usize {
1817 self.cache.len()
1818 }
1819
1820 #[cfg(feature = "wasm")]
1822 pub fn get_macro_count(&self) -> usize {
1823 self.macros.len()
1824 }
1825
1826 pub fn enable_hot_reload(&mut self) {
1828 self.hot_reload_enabled = true;
1829 }
1830
1831 pub fn disable_hot_reload(&mut self) {
1833 self.hot_reload_enabled = false;
1834 }
1835
1836 pub fn is_hot_reload_enabled(&self) -> bool {
1838 self.hot_reload_enabled
1839 }
1840
1841 pub fn render_string_with_debug(&mut self, template: &str, context: &TemplateContext) -> TemplateResult<DebugRenderResult> {
1843 let start_time = SystemTime::now();
1844 let mut debug_info = DebugInfo::new();
1845
1846 debug_info.add_template_processed("inline_template");
1848
1849 debug_info.add_execution_step(ExecutionStep::new("start", "template_render", 1, 1));
1851
1852 let output = self.render_string_with_debug_tracking(template, context, &mut debug_info)?;
1854
1855 if let Ok(duration) = start_time.elapsed() {
1857 debug_info.performance_metrics.total_time_nanos = duration.as_nanos() as u64;
1858 }
1859
1860 debug_info.add_execution_step(ExecutionStep::new("end", "template_render", 1, template.len()));
1862
1863 Ok(DebugRenderResult {
1864 output,
1865 debug_info,
1866 })
1867 }
1868
1869 fn render_string_with_debug_tracking(&mut self, template: &str, context: &TemplateContext, debug_info: &mut DebugInfo) -> TemplateResult<String> {
1871 let mut current_pos = 0;
1876 while let Some(start) = template[current_pos..].find("{{") {
1877 let abs_start = current_pos + start;
1878 if let Some(end) = template[abs_start..].find("}}") {
1879 let var_content = &template[abs_start + 2..abs_start + end];
1880 let (line, column) = find_line_column(template, abs_start);
1881
1882 if let Some(stripped) = var_content.strip_prefix("if ") {
1884 let condition = stripped.trim();
1885 debug_info.add_execution_step(ExecutionStep::new("conditional", condition, line, column));
1886 debug_info.add_variable_access(condition);
1887 } else if let Some(stripped) = var_content.strip_prefix("for ") {
1888 let loop_expr = stripped.trim();
1889 debug_info.add_execution_step(ExecutionStep::new("loop", loop_expr, line, column));
1890 if let Some(in_pos) = loop_expr.find(" in ") {
1891 let array_var = &loop_expr[in_pos + 4..];
1892 debug_info.add_variable_access(array_var.trim());
1893 }
1894 } else if !var_content.starts_with("/") && !var_content.starts_with("!") {
1895 let var_name = var_content.split('|').next().unwrap_or(var_content).trim();
1897 if !var_name.is_empty() {
1898 debug_info.add_execution_step(ExecutionStep::new("variable", var_name, line, column));
1899 debug_info.add_variable_access(var_name);
1900 }
1901 }
1902
1903 current_pos = abs_start + end + 2;
1904 } else {
1905 break;
1906 }
1907 }
1908
1909 self.render_string_original(template, context)
1911 }
1912
1913 pub fn render_v040(&mut self, template_name: &str, context: &TemplateContext) -> TemplateResult<String> {
1915 if self.hot_reload_enabled {
1917 self.check_and_reload_if_needed(template_name)?;
1918 }
1919
1920 match self.load_template_with_enhanced_errors(template_name) {
1922 Ok(template_content) => {
1923 self.render_string_with_error_enhancement(&template_content, context, Some(template_name.to_string()))
1924 },
1925 Err(e) => Err(e),
1926 }
1927 }
1928
1929 fn load_template_with_enhanced_errors(&mut self, template_name: &str) -> TemplateResult<String> {
1931 let template_path = Path::new(&self.template_dir).join(template_name);
1933
1934 if !template_path.exists() {
1935 let available_templates = self.list_available_templates()?;
1937 let suggestions = suggest_templates(template_name, &available_templates, 3);
1938
1939 return Err(TemplateError::TemplateNotFoundWithSuggestions {
1940 template_name: template_name.to_string(),
1941 template_dir: self.template_dir.clone(),
1942 suggestions,
1943 available_templates,
1944 });
1945 }
1946
1947 self.load_template(template_name)
1949 }
1950
1951 fn list_available_templates(&self) -> TemplateResult<Vec<String>> {
1953 let mut templates = Vec::new();
1954 let template_dir = Path::new(&self.template_dir);
1955
1956 if template_dir.exists() && template_dir.is_dir() {
1957 for entry in fs::read_dir(template_dir)? {
1958 let entry = entry?;
1959 let path = entry.path();
1960 if path.is_file() {
1961 if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) {
1962 if file_name.ends_with(".html") || file_name.ends_with(".htm") {
1963 templates.push(file_name.to_string());
1964 }
1965 }
1966 }
1967 }
1968 }
1969
1970 Ok(templates)
1971 }
1972
1973 fn check_and_reload_if_needed(&mut self, template_name: &str) -> TemplateResult<()> {
1975 let template_path = Path::new(&self.template_dir).join(template_name);
1976
1977 if let Ok(metadata) = fs::metadata(&template_path) {
1978 if let Ok(modified) = metadata.modified() {
1979 let should_reload = match self.file_mtimes.get(template_name) {
1980 Some(cached_time) => modified > *cached_time,
1981 None => true,
1982 };
1983
1984 if should_reload {
1985 self.cache.remove(template_name);
1987 self.bytecode_cache.remove(template_name);
1988
1989 self.file_mtimes.insert(template_name.to_string(), modified);
1991
1992 if let Some(dependents) = self.template_dependencies.get(template_name).cloned() {
1994 for dependent in dependents {
1995 self.cache.remove(&dependent);
1996 self.bytecode_cache.remove(&dependent);
1997 }
1998 }
1999 }
2000 }
2001 }
2002
2003 Ok(())
2004 }
2005
2006 pub fn render_string_v040(&mut self, template: &str, context: &TemplateContext) -> TemplateResult<String> {
2008 self.render_string_with_error_enhancement(template, context, None)
2010 }
2011
2012 fn render_string_original(&mut self, template: &str, context: &TemplateContext) -> TemplateResult<String> {
2014 self.parse_and_render_internal(template, context, None)
2016 }
2017
2018 fn render_string_with_error_enhancement(&mut self, template: &str, context: &TemplateContext, template_name: Option<String>) -> TemplateResult<String> {
2020 match self.parse_and_render_internal(template, context, template_name.as_deref()) {
2022 Ok(result) => Ok(result),
2023 Err(TemplateError::Parse(msg)) => {
2024 self.enhance_parse_error(&msg, template, template_name)
2026 },
2027 Err(other) => Err(other),
2028 }
2029 }
2030
2031 fn enhance_parse_error(&self, error_msg: &str, template: &str, template_name: Option<String>) -> TemplateResult<String> {
2033 let (line, column) = if error_msg.contains("unclosed") {
2035 if let Some(pos) = template.find("{{if") {
2037 find_line_column(template, pos)
2038 } else {
2039 (1, 1)
2040 }
2041 } else {
2042 (1, 1)
2043 };
2044
2045 let context_lines = extract_context_lines(template, line, 2);
2046
2047 Err(TemplateError::ParseWithLocation {
2048 message: error_msg.to_string(),
2049 line,
2050 column,
2051 template_name,
2052 context_lines,
2053 })
2054 }
2055
2056 fn parse_and_render_internal(&mut self, template: &str, context: &TemplateContext, _template_name: Option<&str>) -> TemplateResult<String> {
2058 let mut result = template.to_string();
2062
2063 result = self.process_variables(&result, context)?;
2065
2066 if result.contains("{{if") && !result.contains("{{/if}}") {
2068 return Err(TemplateError::Parse("Unclosed {{if}} tag".to_string()));
2069 }
2070
2071 Ok(result)
2072 }
2073
2074 pub fn parse_for_lsp(&mut self, template_content: &str, _file_path: &str) -> TemplateResult<LspParseResult> {
2080 let mut result = LspParseResult::new();
2081
2082 let mut current_pos = 0;
2084 let _line = 1;
2085 let _column = 1;
2086
2087 while let Some(start) = template_content[current_pos..].find("{{") {
2088 let abs_start = current_pos + start;
2089 if let Some(end) = template_content[abs_start..].find("}}") {
2090 let directive_content = &template_content[abs_start + 2..abs_start + end];
2091 let (current_line, current_column) = self.calculate_line_column(template_content, abs_start);
2092
2093 if directive_content.trim().starts_with("if ") {
2095 let condition = directive_content.trim()[3..].trim();
2096 result.add_block(TemplateBlock::new("if", current_line, current_column, condition));
2097 result.add_variable(condition);
2098 } else if directive_content.trim().starts_with("for ") {
2099 let for_expr = directive_content.trim()[4..].trim();
2100 result.add_block(TemplateBlock::new("for", current_line, current_column, for_expr));
2101 if let Some(in_pos) = for_expr.find(" in ") {
2102 let array_var = &for_expr[in_pos + 4..];
2103 result.add_variable(array_var.trim());
2104 }
2105 } else if directive_content.trim().starts_with("macro ") {
2106 let macro_def = directive_content.trim()[6..].trim();
2107 let macro_name = macro_def.split('(').next().unwrap_or(macro_def);
2108 result.macros.push(macro_name.to_string());
2109 } else if !directive_content.starts_with("/") && !directive_content.starts_with("!") {
2110 let parts: Vec<&str> = directive_content.split('|').collect();
2112 let var_name = parts[0].trim();
2113 if !var_name.is_empty() {
2114 result.add_variable(var_name);
2115 }
2116
2117 for filter in parts.iter().skip(1) {
2119 let filter_name = filter.split(':').next().unwrap_or(filter).trim();
2120 result.add_filter(filter_name);
2121 }
2122 }
2123
2124 current_pos = abs_start + end + 2;
2125 } else {
2126 break;
2127 }
2128 }
2129
2130 Ok(result)
2131 }
2132
2133 pub fn get_completions_at_position(&mut self, template: &str, position: usize, context: &TemplateContext) -> TemplateResult<Vec<CompletionItem>> {
2135 let mut completions = Vec::new();
2136
2137 let (current_token, token_type) = self.get_token_at_position(template, position);
2139
2140 match token_type.as_str() {
2141 "variable" => {
2142 for (var_name, var_value) in &context.variables {
2144 if var_name.starts_with(¤t_token) {
2145 let detail = match var_value {
2146 TemplateValue::String(s) => format!("String: {}", s),
2147 TemplateValue::Number(n) => format!("Number: {}", n),
2148 TemplateValue::Bool(b) => format!("Boolean: {}", b),
2149 TemplateValue::Array(_) => "Array".to_string(),
2150 TemplateValue::Object(_) => "Object".to_string(),
2151 };
2152 completions.push(CompletionItem::new(var_name, "variable", &detail));
2153 }
2154 }
2155 },
2156 "filter" => {
2157 let built_in_filters = vec![
2159 ("upper", "Convert text to uppercase"),
2160 ("lower", "Convert text to lowercase"),
2161 ("currency", "Format as currency"),
2162 ("truncate", "Truncate text with ellipsis"),
2163 ("round", "Round numbers to specified decimals"),
2164 ];
2165
2166 for (filter_name, description) in built_in_filters {
2167 if filter_name.starts_with(¤t_token) {
2168 completions.push(CompletionItem::new(filter_name, "filter", description));
2169 }
2170 }
2171 },
2172 "directive" => {
2173 let directives = vec![
2175 ("if", "Conditional rendering"),
2176 ("for", "Loop over arrays"),
2177 ("include", "Include another template"),
2178 ("macro", "Define reusable component"),
2179 ];
2180
2181 for (directive_name, description) in directives {
2182 if directive_name.starts_with(¤t_token) {
2183 completions.push(CompletionItem::new(directive_name, "directive", description));
2184 }
2185 }
2186 },
2187 _ => {}
2188 }
2189
2190 Ok(completions)
2191 }
2192
2193 pub fn tokenize_for_syntax_highlighting(&mut self, template: &str) -> TemplateResult<Vec<SyntaxToken>> {
2195 let mut tokens = Vec::new();
2196 let mut current_pos = 0;
2197
2198 while current_pos < template.len() {
2199 if let Some(start) = template[current_pos..].find("{{") {
2201 let abs_start = current_pos + start;
2202
2203 if start > 0 {
2205 let html_content = &template[current_pos..abs_start];
2206 let (line, column) = self.calculate_line_column(template, current_pos);
2207
2208 if let Some(tag_start) = html_content.find('<') {
2210 if let Some(tag_end) = html_content[tag_start..].find('>') {
2211 let tag = &html_content[tag_start..tag_start + tag_end + 1];
2212 tokens.push(SyntaxToken::new(tag, "html_tag", current_pos + tag_start, line, column));
2213 }
2214 }
2215 }
2216
2217 if let Some(end) = template[abs_start..].find("}}") {
2218 let directive_content = &template[abs_start + 2..abs_start + end];
2219 let (line, column) = self.calculate_line_column(template, abs_start);
2220
2221 if directive_content.contains('|') {
2223 let parts: Vec<&str> = directive_content.split('|').collect();
2225 let var_name = parts[0].trim();
2226 tokens.push(SyntaxToken::new(var_name, "template_variable", abs_start + 2, line, column + 2));
2227
2228 for filter in parts.iter().skip(1) {
2229 let filter_name = filter.split(':').next().unwrap_or(filter).trim();
2230 tokens.push(SyntaxToken::new(filter_name, "template_filter", abs_start + 2, line, column + 2));
2231 }
2232 } else if directive_content.trim().starts_with("if") ||
2233 directive_content.trim().starts_with("for") ||
2234 directive_content.trim().starts_with("/if") ||
2235 directive_content.trim().starts_with("/for") {
2236 tokens.push(SyntaxToken::new(directive_content.trim(), "template_directive", abs_start + 2, line, column + 2));
2237 } else {
2238 tokens.push(SyntaxToken::new(directive_content.trim(), "template_variable", abs_start + 2, line, column + 2));
2240 }
2241
2242 current_pos = abs_start + end + 2;
2243 } else {
2244 break;
2245 }
2246 } else {
2247 break;
2248 }
2249 }
2250
2251 Ok(tokens)
2252 }
2253
2254 pub fn get_syntax_theme_info(&self) -> TemplateResult<HashMap<String, String>> {
2256 let mut theme = HashMap::new();
2257
2258 theme.insert("template_variable".to_string(), "#569cd6".to_string()); theme.insert("template_filter".to_string(), "#4ec9b0".to_string()); theme.insert("template_directive".to_string(), "#c586c0".to_string()); theme.insert("html_tag".to_string(), "#ce9178".to_string()); theme.insert("html_content".to_string(), "#d4d4d4".to_string()); theme.insert("comment".to_string(), "#6a9955".to_string()); Ok(theme)
2267 }
2268
2269 pub fn get_diagnostics_for_editor(&mut self, template: &str, context: &TemplateContext) -> TemplateResult<Vec<Diagnostic>> {
2271 let mut diagnostics = Vec::new();
2272
2273 let mut directive_stack = Vec::new();
2275 let mut current_pos = 0;
2276
2277 while let Some(start) = template[current_pos..].find("{{") {
2278 let abs_start = current_pos + start;
2279 if let Some(end) = template[abs_start..].find("}}") {
2280 let directive_content = &template[abs_start + 2..abs_start + end].trim();
2281 let (line, column) = self.calculate_line_column(template, abs_start);
2282
2283 if directive_content.starts_with("if ") {
2284 directive_stack.push(("if", line, column));
2285 } else if directive_content.starts_with("for ") {
2286 directive_stack.push(("for", line, column));
2287 } else if directive_content.starts_with("/if") {
2288 if let Some((directive_type, _, _)) = directive_stack.pop() {
2289 if directive_type != "if" {
2290 diagnostics.push(Diagnostic::new(
2291 "Mismatched closing directive",
2292 "error",
2293 line,
2294 column
2295 ));
2296 }
2297 } else {
2298 diagnostics.push(Diagnostic::new(
2299 "Unexpected closing directive",
2300 "error",
2301 line,
2302 column
2303 ));
2304 }
2305 } else if directive_content.starts_with("/for") {
2306 if let Some((directive_type, _, _)) = directive_stack.pop() {
2307 if directive_type != "for" {
2308 diagnostics.push(Diagnostic::new(
2309 "Mismatched closing directive",
2310 "error",
2311 line,
2312 column
2313 ));
2314 }
2315 }
2316 } else if !directive_content.starts_with("/") && !directive_content.starts_with("!") {
2317 let parts: Vec<&str> = directive_content.split('|').collect();
2319 let var_name = parts[0].trim();
2320 if !var_name.is_empty() && !context.variables.contains_key(var_name) {
2321 diagnostics.push(Diagnostic::new(
2322 &format!("Unknown variable: {}", var_name),
2323 "warning",
2324 line,
2325 column
2326 ));
2327 }
2328
2329 for filter_part in parts.iter().skip(1) {
2331 let filter_name = filter_part.split(':').next().unwrap_or(filter_part).trim();
2332 if !self.is_known_filter(filter_name) {
2333 diagnostics.push(Diagnostic::new(
2334 &format!("Unknown filter: {}", filter_name),
2335 "error",
2336 line,
2337 column
2338 ));
2339 }
2340 }
2341 }
2342
2343 current_pos = abs_start + end + 2;
2344 } else {
2345 break;
2346 }
2347 }
2348
2349 for (directive_type, line, column) in directive_stack {
2351 diagnostics.push(Diagnostic::new(
2352 &format!("Unclosed {} directive", directive_type),
2353 "error",
2354 line,
2355 column
2356 ));
2357 }
2358
2359 Ok(diagnostics)
2360 }
2361
2362 pub fn get_hover_info_at_position(&mut self, template: &str, position: usize, context: &TemplateContext) -> TemplateResult<HoverInfo> {
2364 let token = self.get_full_token_at_position(template, position);
2365
2366 if let Some(value) = context.variables.get(&token) {
2367 let (var_type, current_value) = match value {
2368 TemplateValue::String(s) => ("String", s.clone()),
2369 TemplateValue::Number(n) => ("Number", n.to_string()),
2370 TemplateValue::Bool(b) => ("Boolean", b.to_string()),
2371 TemplateValue::Array(arr) => ("Array", format!("[{} items]", arr.len())),
2372 TemplateValue::Object(obj) => ("Object", format!("{{{} keys}}", obj.len())),
2373 };
2374
2375 Ok(HoverInfo {
2376 variable_name: token.clone(),
2377 variable_type: var_type.to_string(),
2378 current_value,
2379 description: format!("Template variable of type {}", var_type),
2380 })
2381 } else {
2382 Err(TemplateError::Runtime(format!("No information available for '{}'", token)))
2383 }
2384 }
2385
2386 fn get_full_token_at_position(&self, template: &str, position: usize) -> String {
2388 let mut current_pos = 0;
2389
2390 while let Some(start) = template[current_pos..].find("{{") {
2391 let abs_start = current_pos + start;
2392 if let Some(end) = template[abs_start..].find("}}") {
2393 let abs_end = abs_start + end + 2;
2394
2395 if position >= abs_start && position <= abs_end {
2396 let directive_content = &template[abs_start + 2..abs_start + end];
2397
2398 if directive_content.contains('|') {
2400 let parts: Vec<&str> = directive_content.split('|').collect();
2401 return parts[0].trim().to_string();
2402 } else {
2403 return directive_content.trim().to_string();
2404 }
2405 }
2406
2407 current_pos = abs_end;
2408 } else {
2409 break;
2410 }
2411 }
2412
2413 "".to_string()
2414 }
2415
2416 pub fn get_definition_at_position(&mut self, template: &str, position: usize) -> TemplateResult<DefinitionInfo> {
2418 let token = self.get_full_token_at_position(template, position);
2419
2420 if token.contains('(') {
2422 let macro_name = token.split('(').next().unwrap_or(&token).trim();
2423
2424 let mut current_pos = 0;
2426 while let Some(start) = template[current_pos..].find("{{macro ") {
2427 let abs_start = current_pos + start;
2428 if let Some(end) = template[abs_start..].find("}}") {
2429 let macro_content = &template[abs_start + 8..abs_start + end].trim();
2430 let defined_macro_name = macro_content.split('(').next().unwrap_or(macro_content);
2431
2432 if defined_macro_name.trim() == macro_name {
2433 let (line, column) = self.calculate_line_column(template, abs_start);
2434 return Ok(DefinitionInfo {
2435 definition_type: "macro".to_string(),
2436 name: macro_name.to_string(),
2437 line,
2438 column: column + 8, file_path: None,
2440 });
2441 }
2442
2443 current_pos = abs_start + end + 2;
2444 } else {
2445 break;
2446 }
2447 }
2448 }
2449
2450 Err(TemplateError::Runtime(format!("No definition found for '{}'", token)))
2451 }
2452
2453 fn calculate_line_column(&self, content: &str, position: usize) -> (usize, usize) {
2457 find_line_column(content, position)
2458 }
2459
2460 fn get_token_at_position(&self, template: &str, position: usize) -> (String, String) {
2462 let mut current_pos = 0;
2464
2465 while let Some(start) = template[current_pos..].find("{{") {
2466 let abs_start = current_pos + start;
2467 if let Some(end) = template[abs_start..].find("}}") {
2468 let abs_end = abs_start + end + 2;
2469
2470 if position >= abs_start && position <= abs_end {
2471 let directive_content = &template[abs_start + 2..abs_start + end];
2472 let rel_pos = position - (abs_start + 2);
2473
2474 if directive_content.contains('|') {
2476 let parts: Vec<&str> = directive_content.split('|').collect();
2477 let mut current_char_pos = 0;
2478
2479 for (i, part) in parts.iter().enumerate() {
2480 if rel_pos >= current_char_pos && rel_pos <= current_char_pos + part.len() {
2481 if i == 0 {
2482 let partial_var = &part.trim()[..std::cmp::min(rel_pos.saturating_sub(current_char_pos), part.trim().len())];
2484 return (partial_var.to_string(), "variable".to_string());
2485 } else {
2486 let filter_start = current_char_pos;
2488 let partial_filter = &part.trim()[..std::cmp::min(rel_pos - filter_start, part.trim().len())];
2489 return (partial_filter.to_string(), "filter".to_string());
2490 }
2491 }
2492 current_char_pos += part.len() + 1; }
2494 } else {
2495 let partial_content = &directive_content[..std::cmp::min(rel_pos, directive_content.len())].trim();
2497
2498 let directive_keywords = ["if", "for", "include", "macro"];
2500 let is_potential_directive = directive_keywords.iter().any(|&kw| kw.starts_with(partial_content) || partial_content.is_empty());
2501
2502 if is_potential_directive && !partial_content.contains(' ') {
2503 return (partial_content.to_string(), "directive".to_string());
2504 } else {
2505 return (partial_content.to_string(), "variable".to_string());
2506 }
2507 }
2508 }
2509
2510 current_pos = abs_end;
2511 } else {
2512 break;
2513 }
2514 }
2515
2516 ("".to_string(), "unknown".to_string())
2517 }
2518
2519 fn is_known_filter(&self, filter_name: &str) -> bool {
2521 let known_filters = [
2522 "upper", "lower", "currency", "truncate", "round",
2523 "add", "multiply", "divide", "percentage"
2524 ];
2525
2526 known_filters.contains(&filter_name) || self.custom_filters.contains_key(filter_name)
2527 }
2528
2529 pub fn enable_performance_monitoring(&mut self) {
2554 self.performance_monitoring_enabled = true;
2555 self.memory_usage_tracking = true;
2556 }
2557
2558 pub fn disable_performance_monitoring(&mut self) {
2560 self.performance_monitoring_enabled = false;
2561 self.memory_usage_tracking = false;
2562 self.compilation_stats.clear();
2563 self.render_stats.clear();
2564 }
2565
2566 pub fn get_performance_statistics(&self) -> PerformanceReport {
2577 let mut report = PerformanceReport::new();
2578
2579 for (template_name, (compile_count, _last_time)) in &self.compilation_stats {
2581 report.add_compilation_stat(template_name.clone(), *compile_count);
2582 }
2583
2584 for (template_name, render_times) in &self.render_stats {
2586 if !render_times.is_empty() {
2587 let avg_time = render_times.iter().sum::<u64>() / render_times.len() as u64;
2588 let min_time = *render_times.iter().min().unwrap_or(&0);
2589 let max_time = *render_times.iter().max().unwrap_or(&0);
2590
2591 report.add_render_stat(template_name.clone(), avg_time, min_time, max_time, render_times.len());
2592 }
2593 }
2594
2595 report.cache_size = self.cache.len();
2597 report.bytecode_cache_size = self.bytecode_cache.len();
2598 report.bytecode_cache_enabled = self.bytecode_cache_enabled;
2599
2600 report
2601 }
2602
2603 #[allow(dead_code)]
2605 fn record_render_time(&mut self, template_name: &str, render_time_nanos: u64) {
2606 if self.performance_monitoring_enabled {
2607 self.render_stats
2608 .entry(template_name.to_string())
2609 .or_default()
2610 .push(render_time_nanos);
2611
2612 let stats = self.render_stats.get_mut(template_name).unwrap();
2614 if stats.len() > 100 {
2615 stats.drain(0..stats.len() - 100);
2616 }
2617 }
2618 }
2619
2620 #[allow(dead_code)]
2622 fn record_compilation(&mut self, template_name: &str) {
2623 if self.performance_monitoring_enabled {
2624 let entry = self.compilation_stats
2625 .entry(template_name.to_string())
2626 .or_insert((0, std::time::Instant::now()));
2627 entry.0 += 1;
2628 entry.1 = std::time::Instant::now();
2629 }
2630 }
2631
2632 pub fn optimize_cache(&mut self) -> usize {
2642 let mut optimizations = 0;
2643
2644 if !self.bytecode_cache_enabled {
2646 let frequent_templates: Vec<_> = self.compilation_stats
2647 .iter()
2648 .filter(|(_, (count, _))| *count >= 5) .map(|(name, _)| name.clone())
2650 .collect();
2651
2652 if !frequent_templates.is_empty() {
2653 self.enable_bytecode_cache(true);
2654 optimizations += 1;
2655 }
2656 }
2657
2658 if self.render_stats.len() > 50 {
2660 let mut templates_by_usage: Vec<_> = self.render_stats
2662 .iter()
2663 .map(|(name, times)| (name.clone(), times.len()))
2664 .collect();
2665
2666 templates_by_usage.sort_by(|a, b| b.1.cmp(&a.1));
2667 templates_by_usage.truncate(25);
2668
2669 let keep_templates: std::collections::HashSet<String> =
2670 templates_by_usage.into_iter().map(|(name, _)| name).collect();
2671
2672 self.render_stats.retain(|name, _| keep_templates.contains(name));
2673 optimizations += 1;
2674 }
2675
2676 optimizations
2677 }
2678}
2679
2680#[derive(Debug, Clone)]
2682pub struct PerformanceReport {
2683 pub compilation_stats: HashMap<String, u64>,
2685 pub render_stats: HashMap<String, (u64, u64, u64, usize)>,
2687 pub cache_size: usize,
2689 pub bytecode_cache_size: usize,
2691 pub bytecode_cache_enabled: bool,
2693}
2694
2695impl PerformanceReport {
2696 fn new() -> Self {
2697 Self {
2698 compilation_stats: HashMap::new(),
2699 render_stats: HashMap::new(),
2700 cache_size: 0,
2701 bytecode_cache_size: 0,
2702 bytecode_cache_enabled: false,
2703 }
2704 }
2705
2706 fn add_compilation_stat(&mut self, template_name: String, compile_count: u64) {
2707 self.compilation_stats.insert(template_name, compile_count);
2708 }
2709
2710 fn add_render_stat(&mut self, template_name: String, avg_time: u64, min_time: u64, max_time: u64, count: usize) {
2711 self.render_stats.insert(template_name, (avg_time, min_time, max_time, count));
2712 }
2713
2714 pub fn summary(&self) -> String {
2716 let mut summary = String::new();
2717 summary.push_str("🔮 Performance Report\n");
2718 summary.push_str("==================\n\n");
2719
2720 summary.push_str("📊 Cache Status:\n");
2721 summary.push_str(&format!(" - Template cache: {} entries\n", self.cache_size));
2722 summary.push_str(&format!(" - Bytecode cache: {} entries {}\n",
2723 self.bytecode_cache_size,
2724 if self.bytecode_cache_enabled { "✅" } else { "❌ (disabled)" }));
2725 summary.push('\n');
2726
2727 if !self.render_stats.is_empty() {
2728 summary.push_str("⚡ Top Performing Templates:\n");
2729 let mut sorted_renders: Vec<_> = self.render_stats.iter().collect();
2730 sorted_renders.sort_by(|a, b| a.1.0.cmp(&b.1.0)); for (name, (avg_ns, min_ns, max_ns, count)) in sorted_renders.iter().take(5) {
2733 summary.push_str(&format!(" - {}: {:.2}ms avg ({:.2}-{:.2}ms, {} renders)\n",
2734 name,
2735 *avg_ns as f64 / 1_000_000.0,
2736 *min_ns as f64 / 1_000_000.0,
2737 *max_ns as f64 / 1_000_000.0,
2738 count
2739 ));
2740 }
2741 }
2742
2743 summary
2744 }
2745}