oak-ada 0.0.11

High-performance incremental Ada parser for the oak ecosystem with flexible configuration, emphasizing safety and reliability.
Documentation
#![doc = include_str!("readme.md")]

/// Local definition of highlight kinds
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HighlightKind {
    /// Keyword
    Keyword,
    /// String
    String,
    /// Number
    Number,
    /// Comment
    Comment,
    /// Identifier
    Identifier,
}

/// Highlighter trait
pub trait Highlighter {
    /// Highlight the given text
    fn highlight(&self, text: &str) -> Vec<(usize, usize, HighlightKind)>;
}

/// Ada syntax highlighter
pub struct AdaHighlighter {
    /// Whether to use parser-based highlighting
    pub use_parser: bool,
}

impl Default for AdaHighlighter {
    fn default() -> Self {
        Self { use_parser: false }
    }
}

impl AdaHighlighter {
    /// Creates a new Ada highlighter instance
    pub fn new() -> Self {
        Self::default()
    }

    /// Highlight Ada keywords
    fn highlight_keywords(&self, text: &str) -> Vec<(usize, usize, HighlightKind)> {
        let mut highlights = Vec::new();
        let keywords = [
            "abort",
            "else",
            "new",
            "return",
            "abs",
            "elsif",
            "not",
            "reverse",
            "abstract",
            "end",
            "null",
            "accept",
            "entry",
            "select",
            "access",
            "exception",
            "of",
            "separate",
            "aliased",
            "exit",
            "or",
            "some",
            "all",
            "others",
            "subtype",
            "and",
            "for",
            "out",
            "synchronized",
            "array",
            "function",
            "at",
            "overriding",
            "tagged",
            "generic",
            "package",
            "task",
            "begin",
            "goto",
            "pragma",
            "terminate",
            "body",
            "private",
            "then",
            "if",
            "procedure",
            "type",
            "case",
            "in",
            "protected",
            "constant",
            "interface",
            "until",
            "is",
            "raise",
            "use",
            "declare",
            "range",
            "delay",
            "limited",
            "record",
            "when",
            "delta",
            "loop",
            "rem",
            "while",
            "digits",
            "renames",
            "with",
            "do",
            "mod",
            "requeue",
            "xor",
        ];

        for keyword in &keywords {
            let mut start = 0;
            while let Some(pos) = text[start..].find(keyword) {
                let absolute_pos = start + pos;
                let end_pos = absolute_pos + keyword.len();

                let is_word_boundary_before = absolute_pos == 0 || !text.chars().nth(absolute_pos - 1).unwrap_or(' ').is_alphanumeric();
                let is_word_boundary_after = end_pos >= text.len() || !text.chars().nth(end_pos).unwrap_or(' ').is_alphanumeric();

                if is_word_boundary_before && is_word_boundary_after {
                    highlights.push((absolute_pos, end_pos, HighlightKind::Keyword))
                }

                start = absolute_pos + 1
            }
        }

        highlights
    }

    /// Highlight string literals
    fn highlight_strings(&self, text: &str) -> Vec<(usize, usize, HighlightKind)> {
        let mut highlights = Vec::new();
        let mut chars = text.char_indices().peekable();

        while let Some((i, ch)) = chars.next() {
            if ch == '"' {
                let start = i;
                let mut end = i + 1;

                while let Some((j, next_ch)) = chars.next() {
                    end = j + next_ch.len_utf8();
                    if next_ch == '"' {
                        if let Some(&(_, peek_ch)) = chars.peek() {
                            if peek_ch == '"' {
                                chars.next(); // skip escaped quote
                                continue;
                            }
                        }
                        break;
                    }
                }
                highlights.push((start, end, HighlightKind::String))
            }
        }
        highlights
    }

    /// Highlight number literals
    fn highlight_numbers(&self, text: &str) -> Vec<(usize, usize, HighlightKind)> {
        let mut highlights = Vec::new();
        let mut chars = text.char_indices().peekable();

        while let Some((i, ch)) = chars.next() {
            if ch.is_ascii_digit() {
                let start = i;
                let mut end = i + 1;

                while let Some(&(j, next_ch)) = chars.peek() {
                    if next_ch.is_ascii_digit() || next_ch == '.' || next_ch == '_' || next_ch == '#' || (next_ch >= 'a' && next_ch <= 'f') || (next_ch >= 'A' && next_ch <= 'F') {
                        end = j + next_ch.len_utf8();
                        chars.next();
                    }
                    else {
                        break;
                    }
                }
                highlights.push((start, end, HighlightKind::Number))
            }
        }
        highlights
    }

    /// Highlight comments
    fn highlight_comments(&self, text: &str) -> Vec<(usize, usize, HighlightKind)> {
        let mut highlights = Vec::new();
        let mut pos = 0;

        for line in text.lines() {
            if let Some(comment_start) = line.find("--") {
                let start = pos + comment_start;
                let end = pos + line.len();
                highlights.push((start, end, HighlightKind::Comment))
            }
            pos += line.len() + 1
        }
        highlights
    }

    pub fn highlight_into(&self, text: &str, output: &mut Vec<(usize, usize, HighlightKind)>) {
        let mut highlights = Vec::new();
        highlights.extend(self.highlight_keywords(text));
        highlights.extend(self.highlight_strings(text));
        highlights.extend(self.highlight_numbers(text));
        highlights.extend(self.highlight_comments(text));
        highlights.sort_by_key(|&(start, _, _)| start);
        output.extend(highlights);
    }
}

impl Highlighter for AdaHighlighter {
    fn highlight(&self, text: &str) -> Vec<(usize, usize, HighlightKind)> {
        let mut highlights = Vec::new();
        self.highlight_into(text, &mut highlights);
        highlights
    }
}