1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
//! Syntax highlighting for macro_rules!.
use syntax::{SyntaxElement, SyntaxKind, SyntaxToken, TextRange, T};

use crate::{HlRange, HlTag};

#[derive(Default)]
pub(super) struct MacroHighlighter {
    state: Option<MacroMatcherParseState>,
}

impl MacroHighlighter {
    pub(super) fn init(&mut self) {
        self.state = Some(MacroMatcherParseState::default());
    }

    pub(super) fn advance(&mut self, token: &SyntaxToken) {
        if let Some(state) = self.state.as_mut() {
            update_macro_state(state, token);
        }
    }

    pub(super) fn highlight(&self, element: SyntaxElement) -> Option<HlRange> {
        if let Some(state) = self.state.as_ref() {
            if matches!(state.rule_state, RuleState::Matcher | RuleState::Expander) {
                if let Some(range) = is_metavariable(element) {
                    return Some(HlRange {
                        range,
                        highlight: HlTag::UnresolvedReference.into(),
                        binding_hash: None,
                    });
                }
            }
        }
        None
    }
}

struct MacroMatcherParseState {
    /// Opening and corresponding closing bracket of the matcher or expander of the current rule
    paren_ty: Option<(SyntaxKind, SyntaxKind)>,
    paren_level: usize,
    rule_state: RuleState,
    /// Whether we are inside the outer `{` `}` macro block that holds the rules
    in_invoc_body: bool,
}

impl Default for MacroMatcherParseState {
    fn default() -> Self {
        MacroMatcherParseState {
            paren_ty: None,
            paren_level: 0,
            in_invoc_body: false,
            rule_state: RuleState::None,
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq)]
enum RuleState {
    Matcher,
    Expander,
    Between,
    None,
}

impl RuleState {
    fn transition(&mut self) {
        *self = match self {
            RuleState::Matcher => RuleState::Between,
            RuleState::Expander => RuleState::None,
            RuleState::Between => RuleState::Expander,
            RuleState::None => RuleState::Matcher,
        };
    }
}

fn update_macro_state(state: &mut MacroMatcherParseState, tok: &SyntaxToken) {
    if !state.in_invoc_body {
        if tok.kind() == T!['{'] || tok.kind() == T!['('] {
            state.in_invoc_body = true;
        }
        return;
    }

    match state.paren_ty {
        Some((open, close)) => {
            if tok.kind() == open {
                state.paren_level += 1;
            } else if tok.kind() == close {
                state.paren_level -= 1;
                if state.paren_level == 0 {
                    state.rule_state.transition();
                    state.paren_ty = None;
                }
            }
        }
        None => {
            match tok.kind() {
                T!['('] => {
                    state.paren_ty = Some((T!['('], T![')']));
                }
                T!['{'] => {
                    state.paren_ty = Some((T!['{'], T!['}']));
                }
                T!['['] => {
                    state.paren_ty = Some((T!['['], T![']']));
                }
                _ => (),
            }
            if state.paren_ty.is_some() {
                state.paren_level = 1;
                state.rule_state.transition();
            }
        }
    }
}

fn is_metavariable(element: SyntaxElement) -> Option<TextRange> {
    let tok = element.as_token()?;
    match tok.kind() {
        kind if kind == SyntaxKind::IDENT || kind.is_keyword() => {
            if let Some(_dollar) = tok.prev_token().filter(|t| t.kind() == T![$]) {
                return Some(tok.text_range());
            }
        }
        _ => (),
    };
    None
}