| //! Syntax highlighting for macro_rules!. |
| use syntax::{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, token: &SyntaxToken) -> 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(token) { |
| 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(token: &SyntaxToken) -> Option<TextRange> { |
| match token.kind() { |
| kind if kind.is_any_identifier() => { |
| if let Some(_dollar) = token.prev_token().filter(|t| t.kind() == T![$]) { |
| return Some(token.text_range()); |
| } |
| } |
| _ => (), |
| }; |
| None |
| } |