in starlark/src/syntax/lexer.rs [108:190]
fn calculate_indent(&mut self) -> anyhow::Result<()> {
// consume tabs and spaces, output the indentation levels
let mut it = CursorBytes::new(self.lexer.remainder());
let mut spaces = 0;
let mut tabs = 0;
let mut indent_start = self.lexer.span().end;
loop {
match it.next_char() {
None => {
self.lexer.bump(it.pos());
return Ok(());
}
Some(' ') => {
spaces += 1;
}
Some('\t') => {
tabs += 1;
}
Some('\n') => {
// A line that is entirely blank gets emitted as a newline, and then
// we don't consume the subsequent newline character.
self.lexer.bump(it.pos() - 1);
return Ok(());
}
Some('\r') => {
// We just ignore these entirely
}
Some('#') => {
// A line that is all comments doesn't get emitted at all
// Skip until the next newline
// Remove skip now, so we can freely add it on later
spaces = 0;
tabs = 0;
loop {
match it.next_char() {
None => {
self.lexer.bump(it.pos());
return Ok(());
}
Some('\n') => break, // only the inner loop
Some(_) => {}
}
}
indent_start = self.lexer.span().end + it.pos();
}
_ => break,
}
}
self.lexer.bump(it.pos() - 1); // last character broke us out the loop
let indent = spaces + tabs * 8;
if tabs > 0 && !self.dialect_allow_tabs {
return self.err_pos(LexemeError::InvalidTab, self.lexer.span().start);
}
let now = self.indent_levels.last().copied().unwrap_or(0);
if indent > now {
self.indent_levels.push(indent);
let span = self.lexer.span();
self.buffer
.push_back(Ok((indent_start, Token::Indent, span.end)));
} else if indent < now {
let mut dedents = 1;
self.indent_levels.pop().unwrap();
loop {
let now = self.indent_levels.last().copied().unwrap_or(0);
if now == indent {
break;
} else if now > indent {
dedents += 1;
self.indent_levels.pop().unwrap();
} else {
let pos = self.lexer.span();
return self.err_span(LexemeError::Indentation, pos.start, pos.end);
}
}
for _ in 0..dedents {
// We must declare each dedent is only a position, so multiple adjacent dedents don't overlap
self.buffer
.push_back(Ok((indent_start, Token::Dedent, indent_start)))
}
}
Ok(())
}