omena-scss-eval 0.3.0

Oracle-first SCSS and Less value evaluator rail for Omena CSS
Documentation
use omena_value_lattice::{matching_function_call_end, split_top_level_value_arguments_owned};

pub(crate) fn reduce_static_scss_metadata_with_context<F, M, V, G>(
    value: &str,
    mut function_exists: F,
    mut mixin_exists: M,
    mut variable_exists: V,
    mut global_variable_exists: G,
) -> Option<String>
where
    F: FnMut(&str) -> Option<bool>,
    M: FnMut(&str) -> Option<bool>,
    V: FnMut(&str) -> Option<bool>,
    G: FnMut(&str) -> Option<bool>,
{
    let mut current = value.to_string();
    let mut changed = false;
    for _ in 0..8 {
        let Some(next) = reduce_static_scss_metadata_calls(
            current.as_str(),
            &mut function_exists,
            &mut mixin_exists,
            &mut variable_exists,
            &mut global_variable_exists,
        ) else {
            break;
        };
        if next == current {
            break;
        }
        current = next;
        changed = true;
    }
    changed.then_some(current)
}

fn reduce_static_scss_metadata_calls<F, M, V, G>(
    value: &str,
    function_exists: &mut F,
    mixin_exists: &mut M,
    variable_exists: &mut V,
    global_variable_exists: &mut G,
) -> Option<String>
where
    F: FnMut(&str) -> Option<bool>,
    M: FnMut(&str) -> Option<bool>,
    V: FnMut(&str) -> Option<bool>,
    G: FnMut(&str) -> Option<bool>,
{
    let mut output = String::with_capacity(value.len());
    let mut cursor = 0usize;
    let mut index = 0usize;
    let mut quote: Option<char> = None;
    let mut changed = false;

    while index < value.len() {
        let ch = value[index..].chars().next()?;
        if let Some(quote_ch) = quote {
            index += ch.len_utf8();
            if ch == '\\' {
                if let Some(escaped) = value[index..].chars().next() {
                    index += escaped.len_utf8();
                }
            } else if ch == quote_ch {
                quote = None;
            }
            continue;
        }

        if matches!(ch, '"' | '\'') {
            quote = Some(ch);
            index += ch.len_utf8();
            continue;
        }

        let Some(call) = static_scss_metadata_call_at(value, index) else {
            index += ch.len_utf8();
            continue;
        };
        let left_paren_index = index + call.name.len();
        let Some(close_index) = matching_function_call_end(value, left_paren_index) else {
            index += ch.len_utf8();
            continue;
        };
        let Some(call_value) = value.get(index..close_index + ')'.len_utf8()) else {
            index += ch.len_utf8();
            continue;
        };
        let Some(exists) = resolve_static_scss_metadata_call(
            call_value,
            call.kind,
            function_exists,
            mixin_exists,
            variable_exists,
            global_variable_exists,
        ) else {
            index += ch.len_utf8();
            continue;
        };
        output.push_str(&value[cursor..index]);
        output.push_str(if exists { "true" } else { "false" });
        index = close_index + ')'.len_utf8();
        cursor = index;
        changed = true;
    }

    if !changed {
        return None;
    }
    output.push_str(&value[cursor..]);
    Some(output)
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum StaticScssMetadataKind {
    Function,
    Mixin,
    Variable,
    GlobalVariable,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct StaticScssMetadataCall<'a> {
    name: &'a str,
    kind: StaticScssMetadataKind,
}

fn static_scss_metadata_call_at(value: &str, index: usize) -> Option<StaticScssMetadataCall<'_>> {
    const NAMES: [(&str, StaticScssMetadataKind); 8] = [
        ("meta.function-exists", StaticScssMetadataKind::Function),
        ("function-exists", StaticScssMetadataKind::Function),
        ("meta.mixin-exists", StaticScssMetadataKind::Mixin),
        ("mixin-exists", StaticScssMetadataKind::Mixin),
        ("meta.variable-exists", StaticScssMetadataKind::Variable),
        ("variable-exists", StaticScssMetadataKind::Variable),
        (
            "meta.global-variable-exists",
            StaticScssMetadataKind::GlobalVariable,
        ),
        (
            "global-variable-exists",
            StaticScssMetadataKind::GlobalVariable,
        ),
    ];
    let tail = value.get(index..)?;
    NAMES.iter().find_map(|(name, kind)| {
        (static_scss_metadata_function_left_boundary(value, index)
            && tail.len() > name.len()
            && tail[..name.len()].eq_ignore_ascii_case(name)
            && tail[name.len()..].starts_with('('))
        .then_some(StaticScssMetadataCall { name, kind: *kind })
    })
}

fn static_scss_metadata_function_left_boundary(value: &str, index: usize) -> bool {
    if index == 0 {
        return true;
    }
    value
        .get(..index)
        .and_then(|prefix| prefix.chars().next_back())
        .is_none_or(|ch| !ch.is_ascii_alphanumeric() && !matches!(ch, '_' | '-' | '.'))
}

fn resolve_static_scss_metadata_call<F, M, V, G>(
    value: &str,
    kind: StaticScssMetadataKind,
    function_exists: &mut F,
    mixin_exists: &mut M,
    variable_exists: &mut V,
    global_variable_exists: &mut G,
) -> Option<bool>
where
    F: FnMut(&str) -> Option<bool>,
    M: FnMut(&str) -> Option<bool>,
    V: FnMut(&str) -> Option<bool>,
    G: FnMut(&str) -> Option<bool>,
{
    let trimmed = value.trim();
    let function_name = static_scss_metadata_function_name(trimmed, kind)?;
    let arguments = split_top_level_value_arguments_owned(
        trimmed
            .get(function_name.len()..)?
            .strip_prefix('(')?
            .strip_suffix(')')?,
    )?;
    if arguments.len() != 1 {
        return None;
    }
    let queried_name = parse_static_scss_metadata_name_argument(arguments[0].as_str())?;
    match kind {
        StaticScssMetadataKind::Function => {
            if let Some(exists) = function_exists(queried_name.as_str()) {
                return Some(exists);
            }
            if static_scss_known_global_builtin_function_exists(queried_name.as_str()) {
                return Some(true);
            }
        }
        StaticScssMetadataKind::Mixin => {
            if let Some(exists) = mixin_exists(queried_name.as_str()) {
                return Some(exists);
            }
        }
        StaticScssMetadataKind::Variable => {
            return variable_exists(queried_name.as_str());
        }
        StaticScssMetadataKind::GlobalVariable => {
            return global_variable_exists(queried_name.as_str());
        }
    }
    static_scss_metadata_name_is_safe(queried_name.as_str()).then_some(false)
}

fn static_scss_metadata_function_name(
    value: &str,
    kind: StaticScssMetadataKind,
) -> Option<&'static str> {
    [
        "meta.function-exists",
        "function-exists",
        "meta.mixin-exists",
        "mixin-exists",
        "meta.variable-exists",
        "variable-exists",
        "meta.global-variable-exists",
        "global-variable-exists",
    ]
    .into_iter()
    .find(|name| static_scss_value_starts_with_name(value, name))
    .filter(|name| {
        matches!(
            (kind, *name),
            (
                StaticScssMetadataKind::Function,
                "meta.function-exists" | "function-exists"
            ) | (
                StaticScssMetadataKind::Mixin,
                "meta.mixin-exists" | "mixin-exists"
            ) | (
                StaticScssMetadataKind::Variable,
                "meta.variable-exists" | "variable-exists"
            ) | (
                StaticScssMetadataKind::GlobalVariable,
                "meta.global-variable-exists" | "global-variable-exists"
            )
        )
    })
}

fn static_scss_value_starts_with_name(value: &str, name: &str) -> bool {
    value.len() > name.len()
        && value[..name.len()].eq_ignore_ascii_case(name)
        && value[name.len()..].starts_with('(')
}

fn parse_static_scss_metadata_name_argument(value: &str) -> Option<String> {
    let value = value.trim();
    if value.len() >= 2
        && matches!(value.as_bytes().first(), Some(b'"' | b'\''))
        && value.as_bytes().first() == value.as_bytes().last()
    {
        if value.get(1..value.len() - 1)?.contains('\\') {
            return None;
        }
        return Some(canonical_static_scss_function_name(
            value.get(1..value.len() - 1)?,
        ));
    }
    static_scss_metadata_name_is_safe(value).then(|| canonical_static_scss_metadata_name(value))
}

fn static_scss_metadata_name_is_safe(name: &str) -> bool {
    !name.is_empty()
        && name
            .chars()
            .all(|ch| ch.is_ascii_alphanumeric() || ch == '_' || ch == '-')
}

fn canonical_static_scss_metadata_name(name: &str) -> String {
    name.trim().replace('_', "-")
}

fn canonical_static_scss_function_name(name: &str) -> String {
    canonical_static_scss_metadata_name(name)
}

fn static_scss_known_global_builtin_function_exists(name: &str) -> bool {
    matches!(
        canonical_static_scss_function_name(name).as_str(),
        "abs"
            | "adjust-color"
            | "adjust-hue"
            | "alpha"
            | "append"
            | "blue"
            | "call"
            | "ceil"
            | "comparable"
            | "complement"
            | "darken"
            | "desaturate"
            | "fade-in"
            | "fade-out"
            | "feature-exists"
            | "floor"
            | "function-exists"
            | "global-variable-exists"
            | "grayscale"
            | "green"
            | "hsl"
            | "hsla"
            | "hue"
            | "ie-hex-str"
            | "if"
            | "index"
            | "inspect"
            | "invert"
            | "is-bracketed"
            | "is-superselector"
            | "join"
            | "keywords"
            | "length"
            | "lightness"
            | "lighten"
            | "list-separator"
            | "map-get"
            | "map-has-key"
            | "map-keys"
            | "map-merge"
            | "map-remove"
            | "map-values"
            | "max"
            | "min"
            | "mix"
            | "mixin-exists"
            | "nth"
            | "opacify"
            | "opacity"
            | "percentage"
            | "quote"
            | "random"
            | "red"
            | "rgb"
            | "rgba"
            | "round"
            | "saturation"
            | "saturate"
            | "scale-color"
            | "selector-append"
            | "selector-extend"
            | "selector-nest"
            | "selector-parse"
            | "selector-replace"
            | "selector-unify"
            | "set-nth"
            | "simple-selectors"
            | "str-index"
            | "str-insert"
            | "str-length"
            | "str-slice"
            | "to-lower-case"
            | "to-upper-case"
            | "transparentize"
            | "type-of"
            | "unique-id"
            | "unit"
            | "unitless"
            | "unquote"
            | "variable-exists"
            | "zip"
    )
}