use crate::ast::{Node, NodeKind};
use super::literal_fallback;
pub(super) fn parse_simple_var(text: &str) -> Node {
let param = text.get(1..).unwrap_or("");
Node::empty(NodeKind::ParamExpansion {
param: param.to_string(),
op: None,
arg: None,
})
}
pub(super) fn parse_braced_param(text: &str) -> Node {
let inner = match text.get(2..text.len().saturating_sub(1)) {
Some(s) if !s.is_empty() => s,
_ => return literal_fallback(text),
};
let first = inner.as_bytes().first().copied();
match first {
Some(b'#') if inner.len() > 1 => {
let (param, _) = split_param_and_rest(&inner[1..]);
Node::empty(NodeKind::ParamLength {
param: param.to_string(),
})
}
Some(b'!') if inner.len() > 1 => {
let (param, op, arg) = extract_param_op_arg(&inner[1..]);
Node::empty(NodeKind::ParamIndirect { param, op, arg })
}
_ => {
let (param, op, arg) = extract_param_op_arg(inner);
Node::empty(NodeKind::ParamExpansion { param, op, arg })
}
}
}
fn extract_param_op_arg(s: &str) -> (String, Option<String>, Option<String>) {
let (param, rest) = split_param_and_rest(s);
let (op, arg) = parse_op_and_arg(rest);
(param.to_string(), op, arg)
}
fn split_param_and_rest(s: &str) -> (&str, &str) {
if s.is_empty() {
return ("", "");
}
let bytes = s.as_bytes();
if matches!(bytes[0], b'@' | b'*' | b'?' | b'-' | b'$' | b'!' | b'#') {
return (&s[..1], &s[1..]);
}
if bytes[0].is_ascii_digit() {
let end = bytes
.iter()
.position(|b| !b.is_ascii_digit())
.unwrap_or(s.len());
return maybe_with_subscript(s, end);
}
if bytes[0].is_ascii_alphabetic() || bytes[0] == b'_' {
let end = bytes
.iter()
.position(|b| !b.is_ascii_alphanumeric() && *b != b'_')
.unwrap_or(s.len());
return maybe_with_subscript(s, end);
}
(s, "")
}
fn maybe_with_subscript(s: &str, name_end: usize) -> (&str, &str) {
if s.as_bytes().get(name_end) == Some(&b'[')
&& let Some(bracket_len) = find_matching_bracket(&s[name_end..])
{
let end = name_end + bracket_len + 1;
return (&s[..end], &s[end..]);
}
(&s[..name_end], &s[name_end..])
}
fn find_matching_bracket(s: &str) -> Option<usize> {
let mut depth = 0i32;
for (i, ch) in s.char_indices() {
match ch {
'[' => depth += 1,
']' => {
depth -= 1;
if depth == 0 {
return Some(i);
}
}
_ => {}
}
}
None
}
const OPERATORS: &[&str] = &[
":-", ":=", ":+", ":?", "-", "=", "+", "?", "##", "#", "%%", "%", "//", "/#", "/%", "/", "^^", "^", ",,", ",", "@", ":", ];
fn parse_op_and_arg(s: &str) -> (Option<String>, Option<String>) {
if s.is_empty() {
return (None, None);
}
for op in OPERATORS {
if let Some(arg) = s.strip_prefix(op) {
let arg_opt = if arg.is_empty() {
None
} else {
Some(arg.to_string())
};
return (Some((*op).to_string()), arg_opt);
}
}
(Some(s.to_string()), None)
}