use crate::{helpers::Indent, renderer::is_newline, token::Token};
pub(super) fn limit_indents(buf: &mut [(Token<'_>, Indent)]) {
recurse(buf, true, None);
}
#[derive(Debug)]
enum State {
Skipping,
ScanningMin {
start_index: usize,
min: Indent,
},
Rewriting {
start_index: usize,
delta: Indent,
},
}
fn recurse(
buf: &mut [(Token<'_>, Indent)],
mut last_was_newline: bool,
current_depth: Option<Indent>,
) -> usize {
let current_depth = match current_depth.or(buf.first().map(|(_, v)| *v)) {
Some(v) => v,
None => {
return 0;
}
};
let mut state = State::Skipping;
let mut i = 0;
while i < buf.len() {
let last = last_was_newline;
last_was_newline = is_newline(&buf[i].0);
if !last || is_newline(&buf[i].0) {
i += 1;
continue;
}
let this = buf[i].1;
match state {
State::Skipping if this == current_depth => {}
State::Skipping if this == current_depth + 1 => {
i += recurse(&mut buf[i..], last_was_newline, None);
continue;
}
State::Skipping if this > current_depth + 1 => {
state = State::ScanningMin {
start_index: i,
min: this,
}
}
State::Skipping => {
debug_assert!(this < current_depth);
return i;
}
State::ScanningMin { start_index, min } if this <= current_depth => {
i = start_index;
last_was_newline = true;
let delta = min - current_depth - Indent::new(1);
state = if delta == Indent::ZERO {
i += recurse(&mut buf[start_index..], last_was_newline, Some(min));
State::Skipping
} else {
State::Rewriting { start_index, delta }
};
continue;
}
State::ScanningMin { start_index, min } if min == current_depth + 1 => {
state = State::Skipping;
i = start_index + recurse(&mut buf[start_index..], last_was_newline, None);
continue;
}
State::ScanningMin { start_index, min } => {
state = State::ScanningMin {
start_index,
min: std::cmp::min(min, this),
}
}
State::Rewriting { delta, .. } if this > current_depth => {
debug_assert_ne!(delta, Indent::ZERO); buf[i].1 = this - delta;
}
State::Rewriting { start_index, .. } => {
state = State::Skipping;
i = start_index + recurse(&mut buf[start_index..], last_was_newline, None);
continue;
}
};
i += 1;
}
i
}
#[cfg(test)]
mod tests {
use crate::assert_rewrite;
use super::*;
#[test]
fn test_no_op() {
let tokens = [
(Token::Newline, Indent::new(1)),
(Token::Bang, Indent::new(1)),
(Token::Newline, Indent::new(1)),
(Token::Bang, Indent::new(1)),
(Token::Newline, Indent::new(1)),
(Token::Bang, Indent::new(1)),
];
let mut got = tokens.clone();
limit_indents(&mut got);
assert_eq!(got, tokens);
}
#[test]
fn test_dedent_many() {
let mut tokens = [
(Token::Newline, Indent::new(1)),
(Token::Bang, Indent::new(1)),
(Token::Newline, Indent::new(3)),
(Token::Bang, Indent::new(3)),
(Token::Newline, Indent::new(5)),
(Token::Bang, Indent::new(5)),
(Token::Newline, Indent::new(3)),
(Token::Bang, Indent::new(3)),
(Token::Newline, Indent::new(1)),
(Token::Bang, Indent::new(1)),
];
limit_indents(&mut tokens);
assert_eq!(
tokens,
[
(Token::Newline, Indent::new(1)),
(Token::Bang, Indent::new(1)),
(Token::Newline, Indent::new(3)), (Token::Bang, Indent::new(2)),
(Token::Newline, Indent::new(5)),
(Token::Bang, Indent::new(3)),
(Token::Newline, Indent::new(3)),
(Token::Bang, Indent::new(2)),
(Token::Newline, Indent::new(1)),
(Token::Bang, Indent::new(1)),
]
);
}
#[test]
fn test_dedent_one() {
let mut tokens = [
(Token::Newline, Indent::new(1)),
(Token::Bang, Indent::new(1)),
(Token::Newline, Indent::new(3)),
(Token::Bang, Indent::new(3)),
(Token::Newline, Indent::new(1)),
(Token::Bang, Indent::new(1)),
];
limit_indents(&mut tokens);
assert_eq!(
tokens,
[
(Token::Newline, Indent::new(1)),
(Token::Bang, Indent::new(1)),
(Token::Newline, Indent::new(3)),
(Token::Bang, Indent::new(2)),
(Token::Newline, Indent::new(1)),
(Token::Bang, Indent::new(1)),
]
);
}
#[test]
fn test_step_jump() {
let mut tokens = [
(Token::Newline, Indent::new(1)),
(Token::Bang, Indent::new(1)),
(Token::Newline, Indent::new(2)),
(Token::Bang, Indent::new(2)),
(Token::Newline, Indent::new(5)),
(Token::Bang, Indent::new(5)),
(Token::Newline, Indent::new(5)),
(Token::Bang, Indent::new(5)),
(Token::Newline, Indent::new(1)),
(Token::Bang, Indent::new(1)),
];
limit_indents(&mut tokens);
assert_eq!(
tokens,
[
(Token::Newline, Indent::new(1)),
(Token::Bang, Indent::new(1)),
(Token::Newline, Indent::new(2)),
(Token::Bang, Indent::new(2)),
(Token::Newline, Indent::new(5)),
(Token::Bang, Indent::new(3)),
(Token::Newline, Indent::new(5)),
(Token::Bang, Indent::new(3)),
(Token::Newline, Indent::new(1)),
(Token::Bang, Indent::new(1)),
]
);
}
#[test]
fn test_deferred_indent_use() {
let tokens = [
(Token::Newline, Indent::new(1)),
(Token::Bang, Indent::new(1)),
(Token::Newline, Indent::new(3)),
(Token::Bang, Indent::new(3)),
(Token::Newline, Indent::new(2)),
(Token::Bang, Indent::new(2)),
];
let mut got = tokens.clone();
limit_indents(&mut got);
assert_eq!(got, tokens);
}
#[test]
fn test_let_in_record_literal() {
assert_rewrite!(
r"
---- MODULE Bananas ----
AppendEntries(i, j) ==
/\ LET prevLogIndex == nextIndex[i][j] - 1
prevLogTerm == IF prevLogIndex > 0 THEN
log[i][prevLogIndex].term
ELSE
0
\* Send up to 1 entry, constrained by the end of the log.
lastEntry == Min({Len(log[i]), nextIndex[i][j]})
entries == SubSeq(log[i], nextIndex[i][j], lastEntry)
IN Send([mtype |-> AppendEntriesRequest,
mterm |-> currentTerm[i],
mprevLogIndex |-> prevLogIndex,
mdest |-> j])
====
"
)
}
#[test]
fn test_conj_list_in_bounded_quantification_then_comment() {
assert_rewrite!(
r"
---- MODULE B ----
ClientRejectsBadMetadata ==
/\ BadKey \notin sigs
/\ \A f \in target_files:
/\ BadKey \notin f.sigs
/\ f.version /= InvalidVersion
\* This repro only happens when there's a comment here
====
"
)
}
#[test]
fn test_fairness_body_list() {
assert_rewrite!(
r"
---- MODULE B ----
Fairness ==
\* The TUF repo state shall eventually advance.
/\ WF_vars(
\/ Repo_AddTargetFile
\/ Repo_UpdateSnapshot
\/ Repo_RotateKey_Add
\/ Repo_RotateKey_Remove
)
====
"
)
}
}