//! Rules for helping determine how we're going to lay out the output file.

use crate::OutputSections;
use crate::alignment;
use crate::arch::Architecture;
use crate::error::Context;
use crate::error::Result;
use crate::expression_eval::evaluate_const;
use crate::glob_match::GlobPatternType;
use crate::glob_match::analyze_glob_pattern;
use crate::glob_match::compile_glob_pattern;
use crate::glob_match::unescape_pattern;
use crate::hash::hash_bytes;
use crate::input_data::InputLinkerScript;
use crate::input_data::InputRef;
use crate::linker_script;
use crate::linker_script::ContentsCommand;
use crate::linker_script::SectionCommand;
use crate::output_section_id::OutputSectionId;
use crate::output_section_id::SectionIdentity;
use crate::output_section_id::SectionLocationInfo;
use crate::output_section_id::SectionName;
use crate::parsing::InternalSymDefInfo;
use crate::parsing::ProcessedLinkerScript;
use crate::parsing::Redirect;
use crate::parsing::RedirectKind;
use crate::parsing::SymbolLoc;
use crate::parsing::SymbolPlacement;
use crate::platform::Args as _;
use crate::platform::Platform;
use crate::platform::SectionHeader;
use glob::Pattern;
use hashbrown::HashTable;
use std::borrow::Cow;

pub(crate) struct LayoutRules<'data> {
    pub(crate) section_rules: SectionRules<'data>,
}

#[derive(Default)]
pub(crate) struct LayoutRulesBuilder<'data> {
    rules: Vec<SectionRule<'data>>,
    num_location_counters: usize,
}

#[derive(Debug, Clone, Copy)]
pub(crate) enum SectionKind<'data, P: Platform> {
    /// This is the primary section.
    Primary(SectionIdentity<'data, P>),

    /// This is a secondary section that will be merged into the primary. The ID of the primary is
    /// supplied.
    Secondary(OutputSectionId),
}

/// Rules governing how input sections should be mapped to output sections.
pub(crate) struct SectionRules<'data> {
    /// Rules by the hash of the first 4 bytes of the name.
    rules: HashTable<SectionRule<'data>>,
}

/// Determines how a section name pattern is matched against input section names.
#[derive(Debug, Clone)]
pub(crate) enum SectionNameMatcher<'data> {
    /// Matches sections whose name is exactly equal to the stored bytes.
    Exact(Cow<'data, [u8]>),

    /// Matches sections whose name starts with the stored bytes.
    Prefix(&'data [u8]),

    /// Matches sections whose name matches the glob pattern. The byte slice is the
    /// literal prefix used as hash table key.
    Glob(&'data [u8], Pattern),
}

/// Return the literal byte prefix of this matcher, used for hash table keying.
impl<'data> SectionNameMatcher<'data> {
    fn prefix_bytes(&self) -> &[u8] {
        match self {
            Self::Exact(n) => n.as_ref(),
            Self::Prefix(n) | Self::Glob(n, _) => n,
        }
    }
}

/// A rule for determining what should be done with some input sections.
#[derive(Debug, Clone)]
pub(crate) struct SectionRule<'data> {
    /// Determine how the section rule matches against input section names.
    name_matcher: SectionNameMatcher<'data>,

    /// Pre-compiled glob pattern for matching input filenames. `None` means the rule matches all
    /// files.
    input_file_pattern: Option<Pattern>,

    /// What to do if the rule matches.
    outcome: SectionRuleOutcome,
}

/// What should be done with a particular input section.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum SectionRuleOutcome {
    Section(SectionOutputInfo),
    Discard,
    Custom,
    EhFrame,
    NoteGnuProperty,
    NoteGnuStack,
    Debug,
    DebugIndex,
    RiscVAttribute,
    SortedSection(SectionOutputInfo),
}

impl SectionRuleOutcome {
    pub(crate) fn section_rule_from_id<P: Platform>(
        section_id: OutputSectionId,
        output_info: SectionOutputInfo,
    ) -> SectionRuleOutcome {
        if Some(section_id) == P::EH_FRAME_SECTION_ID {
            SectionRuleOutcome::EhFrame
        } else if Some(section_id) == P::NOTE_GNU_PROPERTY_SECTION_ID {
            SectionRuleOutcome::NoteGnuProperty
        } else if Some(section_id) == P::RISCV_ATTRIBUTES_SECTION_ID {
            SectionRuleOutcome::RiscVAttribute
        } else {
            SectionRuleOutcome::Section(output_info)
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct SectionOutputInfo {
    pub(crate) section_id: OutputSectionId,
    pub(crate) must_keep: bool,
    pub(crate) sorted: bool,
}

impl SectionOutputInfo {
    const fn regular(section_id: OutputSectionId) -> Self {
        Self {
            section_id,
            must_keep: false,
            sorted: false,
        }
    }

    pub(crate) const fn keep(section_id: OutputSectionId) -> Self {
        Self {
            section_id,
            must_keep: true,
            sorted: false,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum LocationCounter<'data> {
    Absolute(linker_script::Expression<'data>, SymbolLoc),
    Relative(linker_script::Expression<'data>, SymbolLoc, OutputSectionId),
}

fn loc_for_global_expr<'data>(
    expr: &crate::linker_script::Expression<'data>,
    section_id: Option<OutputSectionId>,
) -> SymbolLoc {
    let mut loc = SymbolLoc::None;
    expr.visit_expressions(&mut |e| match e {
        crate::linker_script::Expression::SegmentStart(..) => {
            if let Some(section_id) = section_id {
                loc = SymbolLoc::SectionEnd(section_id);
            } else {
                loc = SymbolLoc::FirstSection;
            }
            false
        }
        _ => true,
    });
    loc
}

impl<'data> LayoutRulesBuilder<'data> {
    /// Records information about any sections and symbols declared by the linker script.
    pub(crate) fn process_linker_script<P: Platform>(
        &mut self,
        input: &InputLinkerScript<'data>,
        output_sections: &mut OutputSections<'data, P>,
        args: &P::Args,
    ) -> Result<ProcessedLinkerScript<'data, P>> {
        let mut symbol_defs = Vec::new();
        let mut assertions = Vec::new();
        let mut memory_regions = Vec::new();
        let mut program_headers = Vec::new();
        let mut location_counters = Vec::new();
        let mut ordered_sections = Vec::new();

        let mut current_section_id = None;
        let mut loc = SymbolLoc::FirstSection;
        let mut last_lc_idx = self.num_location_counters;

        for cmd in &input.script.commands {
            if let linker_script::Command::Provide(provide) = cmd {
                let placement = SymbolPlacement::Redirect(Redirect {
                    kind: RedirectKind::Script,
                    expression: provide.value.clone(),
                    loc: loc_for_global_expr(&provide.value, current_section_id),
                });
                symbol_defs.push(
                    crate::parsing::InternalSymDefInfo::new(placement, provide.name)
                        .with_hidden(provide.hidden),
                );
            } else if let linker_script::Command::SymbolDefinition { name, value } = cmd {
                let placement = SymbolPlacement::Redirect(Redirect {
                    kind: RedirectKind::Script,
                    expression: value.to_owned(),
                    loc: loc_for_global_expr(value, current_section_id),
                });
                symbol_defs.push(crate::parsing::InternalSymDefInfo::new(placement, name));
            } else if let linker_script::Command::Sections(sections) = cmd {
                let mut section_start_lc_idx = last_lc_idx;
                let mut prev_phdrs = Vec::new();
                for sec_cmd in &sections.commands {
                    match sec_cmd {
                        SectionCommand::Section(sec) => {
                            if sec.output_section_name == b"/DISCARD/" {
                                for contents_cmd in &sec.commands {
                                    match contents_cmd {
                                        ContentsCommand::Matcher(matcher) => {
                                            for pattern in &matcher.input_section_name_patterns {
                                                self.add_section_rule(SectionRule::new(
                                                    pattern.name,
                                                    matcher.input_file_pattern,
                                                    crate::layout_rules::SectionRuleOutcome::Discard,
                                                )?);
                                            }
                                        }
                                        _ => crate::bail!("Illegal use of /DISCARD/ section"),
                                    }
                                }
                            }
                            let min_alignment =
                                sec.alignment.unwrap_or(alignment::MIN).max(alignment::MIN);

                            let location_info = SectionLocationInfo {
                                location_counters: (section_start_lc_idx, last_lc_idx),
                                location: sec.start_address_expression.clone(),
                                at_location: sec.at_address.clone(),
                                is_top_level: true,
                            };

                            let fill_value = sec
                                .fill
                                .as_ref()
                                .map(|fill| -> Result<[u8; 4]> {
                                    let value = evaluate_const(&fill.value)?;
                                    if value > u64::from(u32::MAX) {
                                        crate::bail!(
                                            "Filler expression result does not fit 32-bit: 0x{:x}",
                                            value
                                        );
                                    }
                                    Ok((value as u32).to_be_bytes())
                                })
                                .transpose()?;

                            if !sec.phdrs.is_empty() {
                                prev_phdrs = sec.phdrs.clone();
                            }
                            let identity = P::section_identity_from_name(SectionName(
                                sec.output_section_name,
                            ))
                            .with_context(|| {
                                format!(
                                    "Output section `{}` cannot be identified from the name alone",
                                    SectionName(sec.output_section_name)
                                )
                            })?;
                            let primary_section_id = output_sections.add_named_section(
                                identity,
                                min_alignment,
                                sec.region,
                                Some(&location_info),
                                fill_value,
                                prev_phdrs.clone(),
                            );
                            ordered_sections.push(primary_section_id);
                            current_section_id = Some(primary_section_id);
                            loc = SymbolLoc::SectionEnd(primary_section_id);

                            let mut last_section_id = None;
                            let mut last_symbol_loc =
                                SymbolLoc::SectionStartRelative(primary_section_id);
                            let mut inner_lc_idx = last_lc_idx;
                            let mut inner_lc_start_idx = last_lc_idx;

                            for contents_cmd in &sec.commands {
                                match contents_cmd {
                                    ContentsCommand::Matcher(matcher) => {
                                        let section_id = if last_section_id.is_none()
                                            && inner_lc_idx == inner_lc_start_idx
                                        {
                                            primary_section_id
                                        } else {
                                            let sec_location_info = SectionLocationInfo {
                                                location_counters: (
                                                    inner_lc_start_idx,
                                                    inner_lc_idx,
                                                ),
                                                location: None,
                                                at_location: None,
                                                is_top_level: false,
                                            };
                                            inner_lc_start_idx = inner_lc_idx;
                                            output_sections.add_secondary_section(
                                                primary_section_id,
                                                alignment::MIN,
                                                None,
                                                Some(sec_location_info),
                                            )
                                        };

                                        for pattern in &matcher.input_section_name_patterns {
                                            let output_info = SectionOutputInfo {
                                                section_id,
                                                must_keep: matcher.must_keep,
                                                sorted: pattern.sorted,
                                            };

                                            let outcome = SectionRuleOutcome::section_rule_from_id::<
                                                P,
                                            >(
                                                primary_section_id, output_info
                                            );

                                            self.add_section_rule(SectionRule::new(
                                                pattern.name,
                                                matcher.input_file_pattern,
                                                outcome,
                                            )?);
                                        }

                                        last_section_id = Some(section_id);
                                        last_symbol_loc = SymbolLoc::SectionEndRelative(section_id);
                                    }
                                    ContentsCommand::SymbolAssignment(assignment) => {
                                        let placement = SymbolPlacement::Redirect(Redirect {
                                            kind: RedirectKind::Script,
                                            expression: assignment.expr.clone(),
                                            loc: last_symbol_loc.clone(),
                                        });
                                        symbol_defs.push(InternalSymDefInfo::new(
                                            placement,
                                            assignment.name,
                                        ));
                                    }
                                    ContentsCommand::Provide(provide) => {
                                        let placement = SymbolPlacement::Redirect(Redirect {
                                            kind: RedirectKind::Script,
                                            expression: provide.value.clone(),
                                            loc: last_symbol_loc.clone(),
                                        });
                                        symbol_defs.push(
                                            InternalSymDefInfo::new(placement, provide.name)
                                                .with_hidden(provide.hidden),
                                        );
                                    }
                                    ContentsCommand::SetLocation(location) => {
                                        location_counters.push(LocationCounter::Relative(
                                            location.address.clone(),
                                            last_symbol_loc,
                                            primary_section_id,
                                        ));
                                        last_symbol_loc = SymbolLoc::LocationCounter(
                                            inner_lc_idx,
                                            Some(primary_section_id),
                                        );
                                        inner_lc_idx += 1;
                                    }
                                    // The CONSTRUCTORS command is used in legacy file formats only.
                                    // On ELF it is a nop.
                                    // (https://sourceware.org/binutils/docs/ld/Output-Section-Keywords.html#index-CONSTRUCTORS)
                                    ContentsCommand::Constructors => (),
                                }
                            }
                            if inner_lc_idx > inner_lc_start_idx {
                                let trailing_lc_info = SectionLocationInfo {
                                    location_counters: (inner_lc_start_idx, inner_lc_idx),
                                    location: None,
                                    at_location: None,
                                    is_top_level: false,
                                };
                                output_sections.add_secondary_section(
                                    primary_section_id,
                                    alignment::MIN,
                                    None,
                                    Some(trailing_lc_info),
                                );
                            }
                            last_lc_idx = inner_lc_idx;
                            section_start_lc_idx = last_lc_idx;
                        }
                        SectionCommand::SetLocation(new_location) => {
                            location_counters
                                .push(LocationCounter::Absolute(new_location.address.clone(), loc));
                            loc = SymbolLoc::LocationCounter(last_lc_idx, None);
                            if current_section_id.is_none() && self.num_location_counters == 0 {
                                output_sections.set_base_address(new_location.address.clone());
                                section_start_lc_idx = location_counters.len();
                            }
                            last_lc_idx += 1;
                        }
                        SectionCommand::Assert(assert_cmd) => {
                            assertions.push(assert_cmd.clone());
                        }
                        SectionCommand::Provide(provide) => {
                            let placement = SymbolPlacement::Redirect(Redirect {
                                kind: RedirectKind::Script,
                                expression: provide.value.clone(),
                                loc: loc.clone(),
                            });
                            symbol_defs.push(
                                InternalSymDefInfo::new(placement, provide.name)
                                    .with_hidden(provide.hidden),
                            );
                        }
                        SectionCommand::SymbolAssignment(assignment) => {
                            let placement = SymbolPlacement::Redirect(Redirect {
                                kind: RedirectKind::Script,
                                expression: assignment.expr.clone(),
                                loc: loc.clone(),
                            });
                            symbol_defs.push(InternalSymDefInfo::new(placement, assignment.name));
                        }
                    }
                }
            } else if let linker_script::Command::Assert(assert_cmd) = cmd {
                assertions.push(assert_cmd.clone());
            } else if let linker_script::Command::Memory(regions) = cmd {
                memory_regions = regions.clone();
            } else if let linker_script::Command::Phdrs(phdrs) = cmd {
                program_headers = phdrs.clone();
            } else if let linker_script::Command::OutputFormat(output_format) = cmd {
                let target_format = match args.output_format_endian() {
                    Some(object::Endianness::Little) => {
                        output_format.little.unwrap_or(output_format.default)
                    }
                    Some(object::Endianness::Big) => {
                        output_format.big.unwrap_or(output_format.default)
                    }
                    None => output_format.default,
                };
                let target_arch = Architecture::parse_output_format(target_format);
                if target_arch == Architecture::Unsupported {
                    crate::bail!(
                        "{} is not yet supported",
                        String::from_utf8_lossy(target_format)
                    );
                }
                if args.architecture() != target_arch {
                    crate::bail!(
                        "Setting the output format using OUTPUT_FORMAT is currently unsupported"
                    );
                }
            }
        }

        self.num_location_counters += location_counters.len();

        Ok(ProcessedLinkerScript {
            symbol_defs,
            assertions,
            input: InputRef {
                file: input.input_file,
                data: input.script_bytes,
                entry: None,
            },
            file_bytes: input.script_bytes,
            memory_regions,
            program_headers,
            location_counters,
            ordered_sections,
        })
    }

    pub(crate) fn build<P: Platform>(mut self, args: &P::Args) -> LayoutRules<'data> {
        let section_rules = if self.rules.is_empty() {
            SectionRules::from_rules(&P::default_layout_rules(args))
        } else {
            P::linker_script_rules_pre_build(&mut self);
            SectionRules::from_rules(&self.rules)
        };

        LayoutRules { section_rules }
    }

    pub(crate) fn add_section_rule(&mut self, rule: SectionRule<'data>) {
        self.rules.push(rule);
    }
}

impl<'data> SectionRule<'data> {
    pub(crate) fn new(
        pattern: &'data [u8],
        input_file_pattern: Option<&'data [u8]>,
        outcome: SectionRuleOutcome,
    ) -> Result<Self> {
        let compiled_file_pattern = input_file_pattern
            .map(|pattern| compile_glob_pattern(pattern).map_err(|e| crate::error!("{e}")))
            .transpose()?;

        let name_matcher = match analyze_glob_pattern(pattern) {
            GlobPatternType::Exact => SectionNameMatcher::Exact(Cow::Borrowed(pattern)),
            GlobPatternType::EscapedExact => {
                SectionNameMatcher::Exact(Cow::Owned(unescape_pattern(pattern)))
            }
            GlobPatternType::Star | GlobPatternType::NonStar => {
                let compiled_pattern =
                    compile_glob_pattern(pattern).map_err(|e| crate::error!("{}", e))?;

                SectionNameMatcher::Glob(pattern, compiled_pattern)
            }
        };

        Ok(Self {
            name_matcher,
            input_file_pattern: compiled_file_pattern,
            outcome,
        })
    }

    #[inline(always)]
    fn matches(&self, section_name: &[u8], file_name: Option<&[u8]>) -> bool {
        let section_matches = match &self.name_matcher {
            SectionNameMatcher::Exact(name) => section_name == name.as_ref(),
            SectionNameMatcher::Prefix(prefix) => section_name.starts_with(prefix),
            SectionNameMatcher::Glob(_, pattern) => {
                if let Ok(name_str) = std::str::from_utf8(section_name) {
                    pattern.matches(name_str)
                } else {
                    false
                }
            }
        };

        if !section_matches {
            return false;
        }

        // If the rule has no file pattern, it matches all files.
        let Some(pattern) = &self.input_file_pattern else {
            return true;
        };

        // If the caller didn't provide a filename, only match rules with no file filter.
        let Some(name) = file_name else {
            return false;
        };

        // Convert the filename bytes to a string for glob matching.
        let Ok(name_str) = std::str::from_utf8(name) else {
            return false;
        };

        pattern.matches(name_str)
    }

    pub(crate) const fn exact_section(
        name: &'data [u8],
        section_id: OutputSectionId,
    ) -> SectionRule<'data> {
        Self::exact(
            name,
            SectionRuleOutcome::Section(SectionOutputInfo::regular(section_id)),
        )
    }

    pub(crate) const fn exact_section_keep(
        name: &'data [u8],
        section_id: OutputSectionId,
    ) -> SectionRule<'data> {
        Self::exact(
            name,
            SectionRuleOutcome::Section(SectionOutputInfo::keep(section_id)),
        )
    }

    pub(crate) const fn prefix_section(
        name: &'data [u8],
        section_id: OutputSectionId,
    ) -> SectionRule<'data> {
        Self::prefix(
            name,
            SectionRuleOutcome::Section(SectionOutputInfo::regular(section_id)),
        )
    }

    pub(crate) const fn prefix_section_sort(
        name: &'data [u8],
        section_id: OutputSectionId,
    ) -> SectionRule<'data> {
        SectionRule {
            name_matcher: SectionNameMatcher::Prefix(name),
            input_file_pattern: None,
            outcome: SectionRuleOutcome::SortedSection(SectionOutputInfo::keep(section_id)),
        }
    }

    pub(crate) const fn exact(
        name: &'data [u8],
        outcome: SectionRuleOutcome,
    ) -> SectionRule<'data> {
        SectionRule {
            name_matcher: SectionNameMatcher::Exact(Cow::Borrowed(name)),
            input_file_pattern: None,
            outcome,
        }
    }

    pub(crate) const fn prefix(
        name: &'data [u8],
        outcome: SectionRuleOutcome,
    ) -> SectionRule<'data> {
        SectionRule {
            name_matcher: SectionNameMatcher::Prefix(name),
            input_file_pattern: None,
            outcome,
        }
    }
}

/// Multiplier for the rule-hashtable's capacity, relative to the number of entries. We want a
/// relatively sparse hashtable, since we may have a small number of entries with the same prefix
/// and thus the same hash. Also, during lookup, if there's no rule with a matching prefix, we want
/// to increase the chances of hitting an empty slot straight away. Experimentally, at least with
/// the built-in rules, multipliers larger than 2 don't further reduce the number of comparisons.
const RULE_TABLE_CAPACITY_MULTIPLIER: usize = 2;

impl<'data> SectionRules<'data> {
    fn from_rules(rules: &[SectionRule<'data>]) -> Self {
        let mut map = SectionRules {
            rules: HashTable::with_capacity(rules.len() * RULE_TABLE_CAPACITY_MULTIPLIER),
        };
        for rule in rules {
            let hash = section_name_prefix_hash(rule.name_matcher.prefix_bytes()).unwrap_or(0);

            map.rules.insert_unique(hash, rule.clone(), |existing| {
                section_name_prefix_hash(existing.name_matcher.prefix_bytes()).unwrap_or(0)
            });
        }

        map
    }

    #[inline(always)]
    pub(crate) fn lookup<P: Platform>(
        &self,
        section_name: &[u8],
        file_name: Option<&[u8]>,
        section_header: &impl SectionHeader,
    ) -> SectionRuleOutcome {
        if section_header.should_exclude() {
            return SectionRuleOutcome::Discard;
        }

        if let Some(hash) = section_name_prefix_hash(section_name)
            && let Some(rule) = self
                .rules
                .find(hash, |rule| rule.matches(section_name, file_name))
        {
            return rule.outcome;
        }

        if section_name.is_empty() {
            return unnamed_section_output::<P>(section_header);
        }

        SectionRuleOutcome::Custom
    }
}

/// Returns a hash of the first four bytes of the supplied name, zero-padding if shorter than 4
/// bytes. Returns `None` if the name is empty.
fn section_name_prefix_hash(name: &[u8]) -> Option<u64> {
    if name.is_empty() {
        return None;
    }
    let mut buf = [0u8; 4];
    let len = name.len().min(4);
    buf[..len].copy_from_slice(&name[..len]);
    Some(hash_bytes(&buf))
}

/// Determines, where if anywhere, we should place an input section with no name.
pub(crate) fn unnamed_section_output<P: Platform>(
    section_header: &impl SectionHeader,
) -> SectionRuleOutcome {
    if !section_header.is_alloc() {
        SectionRuleOutcome::Discard
    } else if section_header.is_prog_bits() {
        if section_header.is_executable() {
            regular_section_or_discard(P::TEXT_SECTION_ID)
        } else if section_header.is_tls() {
            regular_section_or_discard(P::TDATA_SECTION_ID)
        } else if section_header.is_writable() {
            regular_section_or_discard(P::DATA_SECTION_ID)
        } else {
            regular_section_or_discard(P::RODATA_SECTION_ID)
        }
    } else if section_header.is_no_bits() {
        if section_header.is_tls() {
            regular_section_or_discard(P::TBSS_SECTION_ID)
        } else {
            regular_section_or_discard(P::BSS_SECTION_ID)
        }
    } else {
        SectionRuleOutcome::Discard
    }
}

fn regular_section_or_discard(section_id: Option<OutputSectionId>) -> SectionRuleOutcome {
    section_id.map_or(SectionRuleOutcome::Discard, |section_id| {
        SectionRuleOutcome::Section(SectionOutputInfo::regular(section_id))
    })
}

#[test]
fn test_section_mapping() {
    let rules = SectionRules::from_rules(&crate::elf::Elf64::default_layout_rules(
        &crate::args::elf::ElfArgs::new().unwrap(),
    ));
    let header = object::elf::SectionHeader64::<object::LittleEndian> {
        sh_name: Default::default(),
        sh_type: Default::default(),
        sh_flags: Default::default(),
        sh_addr: Default::default(),
        sh_offset: Default::default(),
        sh_size: Default::default(),
        sh_link: Default::default(),
        sh_info: Default::default(),
        sh_addralign: Default::default(),
        sh_entsize: Default::default(),
    };
    let lookup_name =
        |name: &str| rules.lookup::<crate::elf::Elf64>(name.as_bytes(), None, &header);

    assert_eq!(
        lookup_name(".comment"),
        SectionRuleOutcome::Section(SectionOutputInfo {
            section_id: crate::elf::output_section_id::COMMENT,
            must_keep: true,
            sorted: false,
        })
    );
}

#[test]
fn test_glob_section_matching() {
    let rule = SectionRule::new(b".mydata.[0-9]", None, SectionRuleOutcome::Discard).unwrap();

    assert!(rule.matches(b".mydata.0", None));
    assert!(rule.matches(b".mydata.5", None));
    assert!(!rule.matches(b".mydata.A", None));
    assert!(!rule.matches(b".mydata.10", None));
    assert!(!rule.matches(b".mydata.", None));
    assert!(!rule.matches(b".other.0", None));
}

#[test]
fn test_glob_star_anywhere() {
    let rule = SectionRule::new(b".text.*.foo", None, SectionRuleOutcome::Discard).unwrap();
    assert!(rule.matches(b".text.bar.foo", None));
    assert!(rule.matches(b".text.baz.foo", None));
    assert!(!rule.matches(b".text.bar.baz", None));
}

#[test]
fn test_glob_section_character_class() {
    let rule = SectionRule::new(b"foo[_-]bar", None, SectionRuleOutcome::Discard).unwrap();
    assert!(rule.matches(b"foo_bar", None));
    assert!(rule.matches(b"foo-bar", None));
    assert!(!rule.matches(b"foobar", None));
    assert!(!rule.matches(b"foo_barbaz", None));
    assert!(!rule.matches(b"fooxbar", None));

    // [a-z] alphabet range match
    let range_rule = SectionRule::new(b"foo[a-z]bar", None, SectionRuleOutcome::Discard).unwrap();
    assert!(range_rule.matches(b"fooabar", None));
    assert!(range_rule.matches(b"foozbar", None));
    assert!(range_rule.matches(b"foombar", None));
    assert!(!range_rule.matches(b"fooAbar", None));
    assert!(!range_rule.matches(b"foo1bar", None));

    // escaped character match
    let escape_rule = SectionRule::new(b"foo\\*bar", None, SectionRuleOutcome::Discard).unwrap();
    assert!(escape_rule.matches(b"foo*bar", None));
    assert!(!escape_rule.matches(b"fooxbar", None));
    assert!(!escape_rule.matches(b"foobar", None));
}
