use super::language::LanguageSpec;
#[derive(Debug, PartialEq)]
pub(super) enum StringKind {
Double,
Single,
TripleDouble,
TripleSingle,
}
#[derive(Debug, PartialEq)]
pub(super) enum State {
Normal,
InString(StringKind),
InBlockComment(usize), }
pub(super) struct StepResult {
pub advance: usize,
pub new_state: Option<State>,
pub has_code: bool,
pub has_comment: bool,
pub break_line: bool,
}
impl StepResult {
fn code(advance: usize, new_state: Option<State>) -> Self {
Self {
advance,
new_state,
has_code: true,
has_comment: false,
break_line: false,
}
}
fn comment(advance: usize, new_state: Option<State>) -> Self {
Self {
advance,
new_state,
has_code: false,
has_comment: true,
break_line: false,
}
}
fn line_comment() -> Self {
Self {
advance: 0,
new_state: None,
has_code: false,
has_comment: true,
break_line: true,
}
}
}
fn bytes_start_with(haystack: &[u8], needle: &str) -> bool {
haystack.starts_with(needle.as_bytes())
}
fn skip_pragma(bytes: &[u8], start: usize, pclose: &str) -> usize {
let len = bytes.len();
let mut i = start;
while i < len {
if bytes_start_with(&bytes[i..], pclose) {
return i + pclose.len();
}
i += 1;
}
i
}
pub(super) fn step_normal(
rest: &[u8],
spec: &LanguageSpec,
full_bytes: &[u8],
pos: usize,
) -> StepResult {
if spec.triple_quote_strings {
if rest.len() >= 3 && &rest[..3] == b"\"\"\"" {
return StepResult::code(3, Some(State::InString(StringKind::TripleDouble)));
}
if spec.single_quote_strings && rest.len() >= 3 && &rest[..3] == b"'''" {
return StepResult::code(3, Some(State::InString(StringKind::TripleSingle)));
}
}
if let Some((popen, pclose)) = spec.pragma
&& bytes_start_with(rest, popen)
{
let end = skip_pragma(full_bytes, pos + popen.len(), pclose);
return StepResult::code(end - pos, None);
}
if let Some((open, _)) = spec.block_comment
&& bytes_start_with(rest, open)
{
return StepResult::comment(open.len(), Some(State::InBlockComment(1)));
}
let is_line_comment = spec.line_comments.iter().any(|lc| {
if !bytes_start_with(rest, lc) {
return false;
}
if !spec.line_comment_not_before.is_empty()
&& let Some(&next_byte) = rest.get(lc.len())
&& spec.line_comment_not_before.as_bytes().contains(&next_byte)
{
return false;
}
true
});
if is_line_comment {
return StepResult::line_comment();
}
let ch = rest[0];
if ch == b'"' {
return StepResult::code(1, Some(State::InString(StringKind::Double)));
}
if spec.single_quote_strings && ch == b'\'' {
return StepResult::code(1, Some(State::InString(StringKind::Single)));
}
StepResult {
advance: 1,
new_state: None,
has_code: !ch.is_ascii_whitespace(),
has_comment: false,
break_line: false,
}
}
pub(super) fn step_in_string(
rest: &[u8],
ch: u8,
kind: &StringKind,
len: usize,
pos: usize,
) -> StepResult {
match kind {
StringKind::TripleDouble => {
if rest.len() >= 3 && &rest[..3] == b"\"\"\"" {
return StepResult::code(3, Some(State::Normal));
}
}
StringKind::TripleSingle => {
if rest.len() >= 3 && &rest[..3] == b"'''" {
return StepResult::code(3, Some(State::Normal));
}
}
StringKind::Double => {
if ch == b'\\' {
return StepResult::code((pos + 2).min(len) - pos, None);
}
if ch == b'"' {
return StepResult::code(1, Some(State::Normal));
}
}
StringKind::Single => {
if ch == b'\\' {
return StepResult::code((pos + 2).min(len) - pos, None);
}
if ch == b'\'' {
return StepResult::code(1, Some(State::Normal));
}
}
}
StepResult::code(1, None)
}
pub(super) fn step_in_block_comment(rest: &[u8], spec: &LanguageSpec, depth: usize) -> StepResult {
if spec.nested_block_comments
&& let Some((open, _)) = spec.block_comment
&& bytes_start_with(rest, open)
{
return StepResult::comment(open.len(), Some(State::InBlockComment(depth + 1)));
}
if let Some((_, close)) = spec.block_comment
&& bytes_start_with(rest, close)
{
let new_state = if depth <= 1 {
State::Normal
} else {
State::InBlockComment(depth - 1)
};
return StepResult::comment(close.len(), Some(new_state));
}
StepResult::comment(1, None)
}