perl-pragma 0.15.2

Perl pragma extraction and analysis primitives
Documentation
use crate::{
    PragmaState, features_enabled_by_version, parse_perl_version, pragma_arg_items,
    version::DEFAULT_FEATURES,
};

fn feature_items(arg: &str) -> Vec<String> {
    pragma_arg_items(arg)
}

fn known_feature_name(name: &str) -> Option<&'static str> {
    match name {
        "say" => Some("say"),
        "state" => Some("state"),
        "switch" => Some("switch"),
        "smartmatch" => Some("smartmatch"),
        "unicode_strings" => Some("unicode_strings"),
        "unicode_eval" => Some("unicode_eval"),
        "evalbytes" => Some("evalbytes"),
        "current_sub" => Some("current_sub"),
        "fc" => Some("fc"),
        "lexical_subs" => Some("lexical_subs"),
        "postderef" => Some("postderef"),
        "postderef_qq" | "postfix_deref" => Some("postderef_qq"),
        "refaliasing" => Some("refaliasing"),
        "bitwise" => Some("bitwise"),
        "declared_refs" => Some("declared_refs"),
        "try" => Some("try"),
        "signatures" => Some("signatures"),
        "defer" => Some("defer"),
        "isa" => Some("isa"),
        "class" => Some("class"),
        "field" => Some("field"),
        "method" => Some("method"),
        "builtin" | "module_true" => Some("module_true"),
        "indirect" => Some("indirect"),
        "multidimensional" => Some("multidimensional"),
        "bareword_filehandles" => Some("bareword_filehandles"),
        "extra_paired_delimiters" => Some("extra_paired_delimiters"),
        "apostrophe_as_package_separator" => Some("apostrophe_as_package_separator"),
        "keyword_any" => Some("keyword_any"),
        "keyword_all" => Some("keyword_all"),
        _ => None,
    }
}

const ALL_KNOWN_FEATURES: &[&str] = &[
    "say",
    "state",
    "smartmatch",
    "switch",
    "unicode_strings",
    "unicode_eval",
    "evalbytes",
    "current_sub",
    "fc",
    "lexical_subs",
    "postderef",
    "postderef_qq",
    "signatures",
    "refaliasing",
    "bitwise",
    "declared_refs",
    "isa",
    "indirect",
    "multidimensional",
    "bareword_filehandles",
    "try",
    "defer",
    "extra_paired_delimiters",
    "module_true",
    "class",
    "field",
    "method",
    "apostrophe_as_package_separator",
    "keyword_any",
    "keyword_all",
];

pub(crate) fn canonical_feature_query(feature: &str) -> &str {
    match feature {
        "builtin" => "module_true",
        "postfix_deref" => "postderef_qq",
        _ => feature,
    }
}

fn enable_feature_name(state: &mut PragmaState, name: &str) -> bool {
    if name == "signatures" {
        state.signatures_strict = true;
    }
    if name == "unicode_strings" {
        state.unicode_strings = true;
    }

    if let Some(feature) = known_feature_name(name) {
        if state.features.iter().all(|existing| existing != &feature) {
            state.features.push(feature);
        }
        true
    } else {
        false
    }
}

fn disable_feature_name(state: &mut PragmaState, name: &str) -> bool {
    if name == "signatures" {
        state.signatures_strict = false;
    }
    if name == "unicode_strings" {
        state.unicode_strings = false;
    }

    if let Some(feature) = known_feature_name(name) {
        let before = state.features.len();
        state.features.retain(|existing| *existing != feature);
        before != state.features.len()
    } else {
        false
    }
}

pub(crate) fn apply_feature_state(state: &mut PragmaState, args: &[String], enabled: bool) -> bool {
    if !enabled && args.is_empty() {
        let default_features = DEFAULT_FEATURES.to_vec();
        let changed =
            state.features != default_features || state.unicode_strings || state.signatures_strict;
        state.features = default_features;
        state.unicode_strings = state.has_feature("unicode_strings");
        state.signatures_strict = false;
        return changed;
    }

    let mut changed = false;

    for arg in args {
        for item in feature_items(arg) {
            if enabled && item == ":all" {
                for feature in ALL_KNOWN_FEATURES {
                    changed |= enable_feature_name(state, feature);
                }
                continue;
            }

            if enabled && item == ":default" {
                for feature in DEFAULT_FEATURES {
                    changed |= enable_feature_name(state, feature);
                }
                continue;
            }

            if !enabled && item == ":all" {
                let had_features =
                    !state.features.is_empty() || state.unicode_strings || state.signatures_strict;
                state.features.clear();
                state.unicode_strings = false;
                state.signatures_strict = false;
                changed |= had_features;
                continue;
            }

            if !enabled && item == ":default" {
                for feature in DEFAULT_FEATURES {
                    changed |= disable_feature_name(state, feature);
                }
                continue;
            }

            if let Some(version) = item.strip_prefix(':').and_then(parse_perl_version) {
                for feature in features_enabled_by_version(version) {
                    changed |= if enabled {
                        enable_feature_name(state, feature)
                    } else {
                        disable_feature_name(state, feature)
                    };
                }
                continue;
            }

            changed |= if enabled {
                enable_feature_name(state, &item)
            } else {
                disable_feature_name(state, &item)
            };
        }
    }

    changed
}