137 lines
5.1 KiB
Rust
137 lines
5.1 KiB
Rust
// src/parser.rs
|
|
use regex::Regex;
|
|
use std::collections::HashSet;
|
|
|
|
pub struct ScriptParser {
|
|
math_operators: Vec<(Regex, &'static str)>,
|
|
number_re: Regex,
|
|
variable_re: Regex,
|
|
}
|
|
|
|
impl ScriptParser {
|
|
pub fn new() -> Self {
|
|
let math_operators = vec![
|
|
// Basic arithmetic
|
|
(Regex::new(r"\(\s*\+\s+").unwrap(), "(decimal-add "),
|
|
(Regex::new(r"\(\s*-\s+").unwrap(), "(decimal-sub "),
|
|
(Regex::new(r"\(\s*\*\s+").unwrap(), "(decimal-mul "),
|
|
(Regex::new(r"\(\s*/\s+").unwrap(), "(decimal-div "),
|
|
|
|
// Power and advanced operations
|
|
(Regex::new(r"\(\s*\^\s+").unwrap(), "(decimal-pow "),
|
|
(Regex::new(r"\(\s*\*\*\s+").unwrap(), "(decimal-pow "),
|
|
(Regex::new(r"\(\s*pow\s+").unwrap(), "(decimal-pow "),
|
|
(Regex::new(r"\(\s*sqrt\s+").unwrap(), "(decimal-sqrt "),
|
|
|
|
// Logarithmic functions
|
|
(Regex::new(r"\(\s*ln\s+").unwrap(), "(decimal-ln "),
|
|
(Regex::new(r"\(\s*log\s+").unwrap(), "(decimal-ln "),
|
|
(Regex::new(r"\(\s*log10\s+").unwrap(), "(decimal-log10 "),
|
|
(Regex::new(r"\(\s*exp\s+").unwrap(), "(decimal-exp "),
|
|
|
|
// Trigonometric functions
|
|
(Regex::new(r"\(\s*sin\s+").unwrap(), "(decimal-sin "),
|
|
(Regex::new(r"\(\s*cos\s+").unwrap(), "(decimal-cos "),
|
|
(Regex::new(r"\(\s*tan\s+").unwrap(), "(decimal-tan "),
|
|
|
|
// Comparison operators
|
|
(Regex::new(r"\(\s*>\s+").unwrap(), "(decimal-gt "),
|
|
(Regex::new(r"\(\s*<\s+").unwrap(), "(decimal-lt "),
|
|
(Regex::new(r"\(\s*=\s+").unwrap(), "(decimal-eq "),
|
|
(Regex::new(r"\(\s*>=\s+").unwrap(), "(decimal-gte "),
|
|
(Regex::new(r"\(\s*<=\s+").unwrap(), "(decimal-lte "),
|
|
|
|
// Utility functions
|
|
(Regex::new(r"\(\s*abs\s+").unwrap(), "(decimal-abs "),
|
|
(Regex::new(r"\(\s*min\s+").unwrap(), "(decimal-min "),
|
|
(Regex::new(r"\(\s*max\s+").unwrap(), "(decimal-max "),
|
|
(Regex::new(r"\(\s*round\s+").unwrap(), "(decimal-round "),
|
|
];
|
|
|
|
ScriptParser {
|
|
math_operators,
|
|
// VALID REGEX:
|
|
// This captures the preceding delimiter (group 1) and the number (group 2) separately.
|
|
// This avoids lookarounds and allows us to reconstruct the string correctly.
|
|
number_re: Regex::new(r"(^|[\s\(])(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)").unwrap(),
|
|
variable_re: Regex::new(r"\$([^\s)]+)").unwrap(),
|
|
}
|
|
}
|
|
|
|
/// Transform a script by converting math operations and numbers to decimal functions
|
|
pub fn transform(&self, script: &str) -> String {
|
|
let mut transformed = script.to_string();
|
|
|
|
// CORRECT ORDER: Functions first, then variables, then numbers.
|
|
transformed = self.replace_math_functions(&transformed);
|
|
transformed = self.replace_variable_references(&transformed);
|
|
transformed = self.convert_numbers_to_strings(&transformed);
|
|
|
|
transformed
|
|
}
|
|
|
|
/// Convert all unquoted numeric literals to quoted strings
|
|
fn convert_numbers_to_strings(&self, script: &str) -> String {
|
|
let parts: Vec<&str> = script.split('"').collect();
|
|
let mut result = String::new();
|
|
|
|
for (i, part) in parts.iter().enumerate() {
|
|
if i % 2 == 0 {
|
|
// Even indices are outside quotes - process them
|
|
let processed = self.number_re.replace_all(part, |caps: ®ex::Captures| {
|
|
// Reconstruct the string:
|
|
// caps[1] is the delimiter (space, '(', or start of string)
|
|
// caps[2] is the number itself
|
|
// We put the delimiter back and wrap the number in quotes.
|
|
format!("{}\"{}\"", &caps[1], &caps[2])
|
|
});
|
|
result.push_str(&processed);
|
|
} else {
|
|
// Odd indices are inside quotes - keep as is
|
|
result.push_str(part);
|
|
}
|
|
|
|
if i < parts.len() - 1 {
|
|
result.push('"');
|
|
}
|
|
}
|
|
|
|
result
|
|
}
|
|
|
|
/// Replace math function calls with decimal equivalents
|
|
fn replace_math_functions(&self, script: &str) -> String {
|
|
let mut result = script.to_string();
|
|
|
|
for (pattern, replacement) in &self.math_operators {
|
|
result = pattern.replace_all(&result, *replacement).to_string();
|
|
}
|
|
|
|
result
|
|
}
|
|
|
|
/// Replace variable references ($var) with function calls
|
|
fn replace_variable_references(&self, script: &str) -> String {
|
|
self.variable_re.replace_all(script, |caps: ®ex::Captures| {
|
|
format!("(get-var \"{}\")", &caps[1])
|
|
}).to_string()
|
|
}
|
|
|
|
/// Extract dependencies from script (useful for analysis)
|
|
pub fn extract_dependencies(&self, script: &str) -> HashSet<String> {
|
|
let mut dependencies = HashSet::new();
|
|
|
|
for cap in self.variable_re.captures_iter(script) {
|
|
dependencies.insert(cap[1].to_string());
|
|
}
|
|
|
|
dependencies
|
|
}
|
|
}
|
|
|
|
impl Default for ScriptParser {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|