use pyisheval::Value;
pub(super) fn eval_literal(value: &str) -> Value {
let value = value.trim();
if let Some(unquoted) = value.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) {
return Value::StringLit(unquoted.to_string());
}
if value.contains('_') {
return Value::StringLit(value.to_string());
}
if let Ok(f) = value.parse::<f64>() {
return Value::Number(f);
}
if value.eq_ignore_ascii_case("true") {
Value::Number(1.0)
} else if value.eq_ignore_ascii_case("false") {
Value::Number(0.0)
} else {
Value::StringLit(value.to_string())
}
}
pub(super) struct DelimiterTracker {
paren_depth: usize,
bracket_depth: usize,
brace_depth: usize,
in_single_quote: bool,
in_double_quote: bool,
escape_next: bool,
}
impl DelimiterTracker {
pub(super) fn new() -> Self {
Self {
paren_depth: 0,
bracket_depth: 0,
brace_depth: 0,
in_single_quote: false,
in_double_quote: false,
escape_next: false,
}
}
pub(super) fn process(
&mut self,
ch: u8,
) {
if self.escape_next {
self.escape_next = false;
return;
}
match ch {
b'\\' => self.escape_next = true,
b'\'' if !self.in_double_quote => self.in_single_quote = !self.in_single_quote,
b'"' if !self.in_single_quote => self.in_double_quote = !self.in_double_quote,
b'(' if !self.in_single_quote && !self.in_double_quote => self.paren_depth += 1,
b')' if !self.in_single_quote && !self.in_double_quote && self.paren_depth > 0 => {
self.paren_depth -= 1
}
b'[' if !self.in_single_quote && !self.in_double_quote => self.bracket_depth += 1,
b']' if !self.in_single_quote && !self.in_double_quote && self.bracket_depth > 0 => {
self.bracket_depth -= 1
}
b'{' if !self.in_single_quote && !self.in_double_quote => self.brace_depth += 1,
b'}' if !self.in_single_quote && !self.in_double_quote && self.brace_depth > 0 => {
self.brace_depth -= 1
}
_ => {}
}
}
pub(super) fn at_top_level(&self) -> bool {
self.paren_depth == 0
&& self.bracket_depth == 0
&& self.brace_depth == 0
&& !self.in_single_quote
&& !self.in_double_quote
}
pub(super) fn in_string(&self) -> bool {
self.in_single_quote || self.in_double_quote
}
}
pub(crate) fn find_matching_paren(
text: &str,
start: usize,
) -> Option<usize> {
let bytes = text.as_bytes();
if start >= bytes.len() || bytes[start] != b'(' {
return None;
}
let mut tracker = DelimiterTracker::new();
for (i, &ch) in bytes.iter().enumerate().skip(start) {
tracker.process(ch);
if ch == b')'
&& tracker.paren_depth == 0
&& !tracker.in_single_quote
&& !tracker.in_double_quote
{
return Some(i);
}
}
None
}
pub(super) const SUPPORTED_MATH_FUNCS: &[&str] = &[
"atan2", "floor", "acos", "asin", "atan", "ceil", "sqrt", "cos", "sin", "tan", "pow", "log",
"abs",
];
pub(super) fn split_args_balanced(args: &str) -> Vec<&str> {
let mut result = Vec::new();
let mut start = 0;
let bytes = args.as_bytes();
let mut tracker = DelimiterTracker::new();
for (i, &ch) in bytes.iter().enumerate() {
tracker.process(ch);
if ch == b',' && tracker.at_top_level() {
result.push(&args[start..i]);
start = i + 1;
}
}
if start < args.len() {
result.push(&args[start..]);
}
result
}
pub(super) fn escape_python_string(s: &str) -> String {
s.replace('\\', "\\\\")
.replace('\'', "\\'")
.replace('\n', "\\n")
.replace('\r', "\\r")
.replace('\t', "\\t")
}
pub(crate) fn remove_quotes(s: &str) -> &str {
if (s.starts_with('\'') && s.ends_with('\'')) || (s.starts_with('"') && s.ends_with('"')) {
if s.len() >= 2 {
&s[1..s.len() - 1]
} else {
s
}
} else {
s
}
}