remem-ai 0.6.7

Local-first coding agent memory for Claude Code and OpenAI Codex
Documentation
use brush_parser::ast::FunctionBody;

const MAX_DEFAULT_EXPANSION_DEPTH: usize = 8;

pub(super) fn expand_function_body(body: &FunctionBody, arguments: &[String]) -> String {
    let source = body.to_string();
    let mut expanded = String::with_capacity(source.len());
    let mut index = 0;
    let mut single_quoted = false;
    let mut double_quoted = false;
    while index < source.len() {
        let rest = &source[index..];
        let Some(ch) = rest.chars().next() else {
            break;
        };
        if ch == '\'' && !double_quoted {
            single_quoted = !single_quoted;
            expanded.push(ch);
            index += 1;
            continue;
        }
        if ch == '"' && !single_quoted {
            if !double_quoted {
                if let Some((consumed, replacement)) =
                    quoted_collection_replacement(rest, arguments)
                {
                    expanded.push_str(&replacement);
                    index += consumed;
                    continue;
                }
            }
            double_quoted = !double_quoted;
            expanded.push(ch);
            index += 1;
            continue;
        }
        if ch == '\\' {
            expanded.push(ch);
            index += 1;
            if let Some(next) = source[index..].chars().next() {
                expanded.push(next);
                index += next.len_utf8();
            }
            continue;
        }
        if ch == '$' && !single_quoted {
            if let Some((consumed, replacement)) =
                positional_replacement(rest, arguments, double_quoted, 0)
            {
                expanded.push_str(&replacement);
                index += consumed;
                continue;
            }
        }
        expanded.push(ch);
        index += ch.len_utf8();
    }
    expanded
}

fn quoted_collection_replacement(rest: &str, arguments: &[String]) -> Option<(usize, String)> {
    for pattern in ["\"${@}\"", "\"$@\""] {
        if rest.starts_with(pattern) {
            return Some((pattern.len(), quote_arguments(arguments)));
        }
    }
    for pattern in ["\"${*}\"", "\"$*\""] {
        if rest.starts_with(pattern) {
            return Some((pattern.len(), quote_argument(&arguments.join(" "))));
        }
    }
    None
}

fn positional_replacement(
    rest: &str,
    arguments: &[String],
    double_quoted: bool,
    depth: usize,
) -> Option<(usize, String)> {
    for pattern in ["${@}", "$@", "${*}", "$*"] {
        if rest.starts_with(pattern) {
            let replacement = if double_quoted {
                if pattern.contains('@') {
                    expand_at_in_double_quotes(arguments)
                } else {
                    escape_double_quoted(&arguments.join(" "))
                }
            } else {
                split_arguments(arguments)
            };
            return Some((pattern.len(), replacement));
        }
    }

    let (consumed, expression) = parameter_expression(rest)?;
    let value = resolve_parameter_expression(expression, arguments, depth)?;
    let replacement = if double_quoted {
        escape_double_quoted(&value)
    } else {
        split_argument(&value)
    };
    Some((consumed, replacement))
}

fn parameter_expression(rest: &str) -> Option<(usize, &str)> {
    let parameter = rest.strip_prefix('$')?;
    if parameter.starts_with('{') {
        let end = matching_parameter_brace(rest)?;
        return Some((end + 1, &rest[2..end]));
    }
    let first = parameter.chars().next()?;
    first
        .is_ascii_digit()
        .then_some((1 + first.len_utf8(), &parameter[..first.len_utf8()]))
}

fn matching_parameter_brace(source: &str) -> Option<usize> {
    let mut index = 2;
    let mut depth = 1;
    while index < source.len() {
        let rest = &source[index..];
        if rest.starts_with("${") {
            depth += 1;
            index += 2;
            continue;
        }
        let ch = rest.chars().next()?;
        if ch == '}' {
            depth -= 1;
            if depth == 0 {
                return Some(index);
            }
        }
        index += ch.len_utf8();
    }
    None
}

fn resolve_parameter_expression(
    expression: &str,
    arguments: &[String],
    depth: usize,
) -> Option<String> {
    if depth > MAX_DEFAULT_EXPANSION_DEPTH {
        return None;
    }
    let digit_end = expression
        .find(|ch: char| !ch.is_ascii_digit())
        .unwrap_or(expression.len());
    let digits = &expression[..digit_end];
    let position = digits.parse::<usize>().ok()?;
    if position == 0 {
        return None;
    }
    let argument = arguments.get(position - 1);
    let suffix = &expression[digit_end..];
    let selected = if let Some(fallback) = suffix.strip_prefix(":-") {
        if argument.is_none_or(String::is_empty) {
            return expand_default_value(fallback, arguments, depth + 1);
        }
        argument.map(String::as_str)
    } else if let Some(fallback) = suffix.strip_prefix('-') {
        if argument.is_none() {
            return expand_default_value(fallback, arguments, depth + 1);
        }
        argument.map(String::as_str)
    } else if suffix.is_empty() {
        argument.map(String::as_str).or(Some(""))
    } else {
        return None;
    };
    Some(selected.unwrap_or("").to_string())
}

fn expand_default_value(source: &str, arguments: &[String], depth: usize) -> Option<String> {
    if depth > MAX_DEFAULT_EXPANSION_DEPTH {
        return None;
    }
    let mut expanded = String::with_capacity(source.len());
    let mut index = 0;
    let mut single_quoted = false;
    let mut double_quoted = false;
    while index < source.len() {
        let rest = &source[index..];
        let ch = rest.chars().next()?;
        if ch == '\'' && !double_quoted {
            single_quoted = !single_quoted;
            index += 1;
            continue;
        }
        if ch == '"' && !single_quoted {
            double_quoted = !double_quoted;
            index += 1;
            continue;
        }
        if ch == '\\' && !single_quoted {
            let next = source[index + 1..].chars().next()?;
            expanded.push(next);
            index += 1 + next.len_utf8();
            continue;
        }
        if ch == '$' && !single_quoted {
            if let Some(pattern) = ["${@}", "$@", "${*}", "$*"]
                .into_iter()
                .find(|pattern| rest.starts_with(pattern))
            {
                expanded.push_str(&arguments.join(" "));
                index += pattern.len();
                continue;
            }
            let (consumed, expression) = parameter_expression(rest)?;
            expanded.push_str(&resolve_parameter_expression(expression, arguments, depth)?);
            index += consumed;
            continue;
        }
        if ch == '`' && !single_quoted {
            return None;
        }
        expanded.push(ch);
        index += ch.len_utf8();
    }
    (!single_quoted && !double_quoted).then_some(expanded)
}

fn quote_arguments(arguments: &[String]) -> String {
    arguments
        .iter()
        .map(|argument| quote_argument(argument))
        .collect::<Vec<_>>()
        .join(" ")
}

fn split_arguments(arguments: &[String]) -> String {
    arguments
        .iter()
        .map(|argument| split_argument(argument))
        .filter(|argument| !argument.is_empty())
        .collect::<Vec<_>>()
        .join(" ")
}

fn split_argument(value: &str) -> String {
    value
        .split([' ', '\t', '\n'])
        .filter(|word| !word.is_empty())
        .map(quote_argument)
        .collect::<Vec<_>>()
        .join(" ")
}

fn expand_at_in_double_quotes(arguments: &[String]) -> String {
    let Some((first, remaining)) = arguments.split_first() else {
        return String::new();
    };
    let Some((last, middle)) = remaining.split_last() else {
        return escape_double_quoted(first);
    };

    let mut expanded = escape_double_quoted(first);
    expanded.push_str("\" ");
    for argument in middle {
        expanded.push_str(&quote_argument(argument));
        expanded.push(' ');
    }
    expanded.push_str(&quote_argument(last));
    expanded.push('"');
    expanded
}

fn escape_double_quoted(value: &str) -> String {
    let mut escaped = String::with_capacity(value.len());
    for ch in value.chars() {
        if matches!(ch, '\\' | '"' | '$' | '`') {
            escaped.push('\\');
        }
        escaped.push(ch);
    }
    escaped
}

fn quote_argument(value: &str) -> String {
    format!("'{}'", value.replace('\'', "'\\''"))
}