#[derive(Clone, Copy)]
pub(crate) enum ScanState {
Normal,
Slash,
LineComment,
BlockComment {
depth: u32,
},
BlockCommentStar {
depth: u32,
},
BlockCommentSlash {
depth: u32,
},
InString,
InStringEscape,
Tick,
InChar,
InCharEscape,
SeenR,
RawStringHashes {
count: u32,
},
InRawString {
hashes: u32,
},
RawStringClosing {
hashes: u32,
seen: u32,
},
}
pub(crate) struct StateAction {
pub(crate) next: ScanState,
pub(crate) brace: BraceAction,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum BraceAction {
None,
Open,
Close,
}
impl ScanState {
pub(crate) fn advance(self, ch: char) -> StateAction {
match self {
Self::Normal => Self::advance_normal(ch),
Self::Slash => Self::advance_slash(ch),
Self::LineComment => Self::advance_line_comment(ch),
Self::BlockComment { depth } => Self::advance_block_comment(depth, ch),
Self::BlockCommentStar { depth } => Self::advance_block_comment_star(depth, ch),
Self::BlockCommentSlash { depth } => Self::advance_block_comment_slash(depth, ch),
Self::InString => Self::advance_in_string(ch),
Self::InStringEscape => StateAction {
next: Self::InString,
brace: BraceAction::None,
},
Self::Tick => Self::advance_tick(ch),
Self::InChar => Self::advance_in_char(ch),
Self::InCharEscape => StateAction {
next: Self::InChar,
brace: BraceAction::None,
},
Self::SeenR => Self::advance_seen_r(ch),
Self::RawStringHashes { count } => Self::advance_raw_string_hashes(count, ch),
Self::InRawString { hashes } => Self::advance_in_raw_string(hashes, ch),
Self::RawStringClosing { hashes, seen } => {
Self::advance_raw_string_closing(hashes, seen, ch)
}
}
}
fn advance_normal(ch: char) -> StateAction {
let (next, brace) = match ch {
'{' => (Self::Normal, BraceAction::Open),
'}' => (Self::Normal, BraceAction::Close),
'/' => (Self::Slash, BraceAction::None),
'"' => (Self::InString, BraceAction::None),
'\'' => (Self::Tick, BraceAction::None),
'r' => (Self::SeenR, BraceAction::None),
_ => (Self::Normal, BraceAction::None),
};
StateAction { next, brace }
}
fn advance_slash(ch: char) -> StateAction {
let (next, brace) = match ch {
'/' => (Self::LineComment, BraceAction::None),
'*' => (Self::BlockComment { depth: 1 }, BraceAction::None),
'{' => (Self::Normal, BraceAction::Open),
'}' => (Self::Normal, BraceAction::Close),
'"' => (Self::InString, BraceAction::None),
'\'' => (Self::Tick, BraceAction::None),
'r' => (Self::SeenR, BraceAction::None),
_ => (Self::Normal, BraceAction::None),
};
StateAction { next, brace }
}
fn advance_line_comment(ch: char) -> StateAction {
let next = if ch == '\n' {
Self::Normal
} else {
Self::LineComment
};
StateAction {
next,
brace: BraceAction::None,
}
}
fn advance_block_comment(depth: u32, ch: char) -> StateAction {
let next = match ch {
'*' => Self::BlockCommentStar { depth },
'/' => Self::BlockCommentSlash { depth },
_ => Self::BlockComment { depth },
};
StateAction {
next,
brace: BraceAction::None,
}
}
fn advance_block_comment_star(depth: u32, ch: char) -> StateAction {
let next = match ch {
'/' if depth == 1 => Self::Normal,
'/' => Self::BlockComment { depth: depth - 1 },
'*' => Self::BlockCommentStar { depth },
_ => Self::BlockComment { depth },
};
StateAction {
next,
brace: BraceAction::None,
}
}
fn advance_block_comment_slash(depth: u32, ch: char) -> StateAction {
let next = match ch {
'*' => Self::BlockComment { depth: depth + 1 },
'/' => Self::BlockCommentSlash { depth },
_ => Self::BlockComment { depth },
};
StateAction {
next,
brace: BraceAction::None,
}
}
fn advance_in_string(ch: char) -> StateAction {
let next = match ch {
'\\' => Self::InStringEscape,
'"' => Self::Normal,
_ => Self::InString,
};
StateAction {
next,
brace: BraceAction::None,
}
}
fn advance_tick(ch: char) -> StateAction {
let next = match ch {
'\\' => Self::InCharEscape,
'\'' => Self::Normal,
_ => Self::InChar,
};
StateAction {
next,
brace: BraceAction::None,
}
}
fn advance_in_char(ch: char) -> StateAction {
if ch == '\'' {
return StateAction {
next: Self::Normal,
brace: BraceAction::None,
};
}
Self::advance_normal(ch)
}
fn advance_seen_r(ch: char) -> StateAction {
match ch {
'"' => StateAction {
next: Self::InRawString { hashes: 0 },
brace: BraceAction::None,
},
'#' => StateAction {
next: Self::RawStringHashes { count: 1 },
brace: BraceAction::None,
},
'{' => StateAction {
next: Self::Normal,
brace: BraceAction::Open,
},
'}' => StateAction {
next: Self::Normal,
brace: BraceAction::Close,
},
'/' => StateAction {
next: Self::Slash,
brace: BraceAction::None,
},
'\'' => StateAction {
next: Self::Tick,
brace: BraceAction::None,
},
'r' => StateAction {
next: Self::SeenR,
brace: BraceAction::None,
},
_ => StateAction {
next: Self::Normal,
brace: BraceAction::None,
},
}
}
fn advance_raw_string_hashes(count: u32, ch: char) -> StateAction {
match ch {
'#' => StateAction {
next: Self::RawStringHashes { count: count + 1 },
brace: BraceAction::None,
},
'"' => StateAction {
next: Self::InRawString { hashes: count },
brace: BraceAction::None,
},
'{' => StateAction {
next: Self::Normal,
brace: BraceAction::Open,
},
'}' => StateAction {
next: Self::Normal,
brace: BraceAction::Close,
},
'/' => StateAction {
next: Self::Slash,
brace: BraceAction::None,
},
'\'' => StateAction {
next: Self::Tick,
brace: BraceAction::None,
},
'r' => StateAction {
next: Self::SeenR,
brace: BraceAction::None,
},
_ => StateAction {
next: Self::Normal,
brace: BraceAction::None,
},
}
}
fn advance_in_raw_string(hashes: u32, ch: char) -> StateAction {
let next = if ch == '"' {
if hashes == 0 {
Self::Normal
} else {
Self::RawStringClosing { hashes, seen: 0 }
}
} else {
Self::InRawString { hashes }
};
StateAction {
next,
brace: BraceAction::None,
}
}
fn advance_raw_string_closing(hashes: u32, seen: u32, ch: char) -> StateAction {
if ch == '#' {
let new_seen = seen + 1;
if new_seen == hashes {
return StateAction {
next: Self::Normal,
brace: BraceAction::None,
};
}
return StateAction {
next: Self::RawStringClosing {
hashes,
seen: new_seen,
},
brace: BraceAction::None,
};
}
if ch == '"' {
return StateAction {
next: Self::RawStringClosing { hashes, seen: 0 },
brace: BraceAction::None,
};
}
StateAction {
next: Self::InRawString { hashes },
brace: BraceAction::None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn scan_braces(source: &str) -> Vec<(usize, BraceAction)> {
let mut state = ScanState::Normal;
let mut out = Vec::new();
for (idx, ch) in source.char_indices() {
let action = state.advance(ch);
state = action.next;
if action.brace != BraceAction::None {
out.push((idx, action.brace));
}
}
out
}
#[test]
fn raw_string_braces_ignored() {
let src = r##"let ss = r#"}"#; fn foo() {}"##;
let braces = scan_braces(src);
assert_eq!(braces.len(), 2);
assert!(braces.first().is_some_and(|bb| bb.1 == BraceAction::Open));
assert!(braces.last().is_some_and(|bb| bb.1 == BraceAction::Close));
}
#[test]
fn raw_string_no_hashes() {
let src = "let ss = r\"}\"; fn foo() {}";
let braces = scan_braces(src);
assert_eq!(braces.len(), 2);
}
#[test]
fn raw_string_multiple_hashes() {
let src = r###"let ss = r##"}"##; fn foo() {}"###;
let braces = scan_braces(src);
assert_eq!(braces.len(), 2);
}
#[test]
fn nested_block_comment() {
let src = "/* /* } */ } */ fn foo() {}";
let braces = scan_braces(src);
assert_eq!(braces.len(), 2);
}
#[test]
fn lifetime_followed_by_brace() {
let src = "fn foo<'a>() {}";
let braces = scan_braces(src);
assert_eq!(braces.len(), 2);
assert!(braces.first().is_some_and(|bb| bb.1 == BraceAction::Open));
assert!(braces.last().is_some_and(|bb| bb.1 == BraceAction::Close));
}
#[test]
fn byte_raw_string_handled() {
let hash = '#';
let quote = '\"';
let src = format!("let ss = br{hash}{quote}}}{quote}{hash}; fn foo() {{}}");
let braces = scan_braces(&src);
assert_eq!(braces.len(), 2);
}
}