Skip to content

Commit

Permalink
Merge 9e33d67 into ea7a8bd
Browse files Browse the repository at this point in the history
  • Loading branch information
ScottCarda-MS authored Mar 24, 2024
2 parents ea7a8bd + 9e33d67 commit 4d93cad
Show file tree
Hide file tree
Showing 65 changed files with 1,016 additions and 864 deletions.
142 changes: 93 additions & 49 deletions compiler/qsc_formatter/src/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub fn calculate_format_edits(code: &str) -> Vec<TextEdit> {
indent_level: 0,
delim_newlines_stack: vec![],
type_param_state: TypeParameterListState::NoState,
spec_decl_state: SpecDeclState::NoState,
};

// The sliding window used is over three adjacent tokens
Expand Down Expand Up @@ -143,6 +144,21 @@ enum TypeParameterListState {
InTypeParamList,
}

/// This is to keep track of whether or not the formatter
/// is currently processing a functor specialization
/// declaration.
#[derive(Clone, Copy)]
enum SpecDeclState {
/// Not in a location relevant to this state.
NoState,
/// Formatter is on the specialization keyword.
/// (it is the left-hand token)
OnSpecKeyword,
/// Formatter is on the specialization ellipse.
/// (it is the left-hand token)
OnEllipse,
}

/// Enum for a token's status as a delimiter.
/// `<` and `>` are delimiters only with type-parameter lists,
/// which is determined using the TypeParameterListState enum.
Expand Down Expand Up @@ -185,6 +201,7 @@ struct Formatter<'a> {
indent_level: usize,
delim_newlines_stack: Vec<NewlineContext>,
type_param_state: TypeParameterListState,
spec_decl_state: SpecDeclState,
}

impl<'a> Formatter<'a> {
Expand All @@ -205,6 +222,8 @@ impl<'a> Formatter<'a> {
let are_newlines_in_spaces = whitespace.contains('\n');
let does_right_required_newline = matches!(&right.kind, Syntax(cooked_right) if is_newline_keyword_or_ampersat(cooked_right));

self.update_spec_decl_state(&left.kind);

let (left_delim_state, right_delim_state) =
self.update_type_param_state(&left.kind, &right.kind);

Expand Down Expand Up @@ -357,27 +376,38 @@ impl<'a> Formatter<'a> {
| (_, Keyword(Keyword::Slf)) => {
effect_single_space(left, whitespace, right, &mut edits);
}
(_, _) if are_newlines_in_spaces => {
effect_trim_whitespace(left, whitespace, right, &mut edits);
// Ignore the rest of the cases if the user has a newline in the whitespace.
// This is done because we don't currently have logic for determining when
// lines are too long and require newlines, and we don't have logic
// for determining what the correct indentation should be in these cases,
// so we put this do-nothing case in to leave user code unchanged.
(Close(Delim::Brace), _)
if is_newline_after_brace(cooked_right, right_delim_state) =>
{
effect_correct_indentation(
left,
whitespace,
right,
&mut edits,
self.indent_level,
);
}
(String(StringToken::Interpolated(_, InterpolatedEnding::LBrace)), _)
| (_, String(StringToken::Interpolated(InterpolatedStart::RBrace, _))) => {
effect_no_space(left, whitespace, right, &mut edits);
}
(_, Open(Delim::Brace)) => {
// Special-case braces to always have a leading single space
(DotDotDot, _) if matches!(self.spec_decl_state, SpecDeclState::OnEllipse) => {
// Special-case specialization declaration ellipses to have a space after
effect_single_space(left, whitespace, right, &mut edits);
}
(_, Open(Delim::Brace)) => {
// Special-case braces to have a leading single space with values
if is_prefix(cooked_left) {
effect_no_space(left, whitespace, right, &mut edits);
} else {
effect_single_space(left, whitespace, right, &mut edits);
}
}
(_, _) if matches!(right_delim_state, Delimiter::Open) => {
// Otherwise, all open delims have the same logic
if is_value_token_left(cooked_left, left_delim_state) || is_prefix(cooked_left)
{
// i.e. foo() or { foo }[3]
// i.e. foo() or foo[3]
effect_no_space(left, whitespace, right, &mut edits);
} else {
// i.e. let x = (1, 2, 3);
Expand All @@ -391,14 +421,10 @@ impl<'a> Formatter<'a> {
effect_single_space(left, whitespace, right, &mut edits);
}
}
(_, Keyword(Keyword::Is))
| (_, Keyword(Keyword::For))
| (_, Keyword(Keyword::While))
| (_, Keyword(Keyword::Repeat))
| (_, Keyword(Keyword::If))
| (_, Keyword(Keyword::Within))
| (_, Keyword(Keyword::Return))
| (_, Keyword(Keyword::Fail)) => {
(_, Keyword(Keyword::Is)) => {
effect_single_space(left, whitespace, right, &mut edits);
}
(_, Keyword(keyword)) if is_starter_keyword(keyword) => {
effect_single_space(left, whitespace, right, &mut edits);
}
(_, _) if is_value_token_right(cooked_right, right_delim_state) => {
Expand All @@ -421,6 +447,18 @@ impl<'a> Formatter<'a> {
(_, _) if is_prefix_without_space(cooked_right) => {
effect_no_space(left, whitespace, right, &mut edits);
}
(_, Keyword(keyword)) if is_prefix_keyword(keyword) => {
effect_single_space(left, whitespace, right, &mut edits);
}
(_, WSlash) if are_newlines_in_spaces => {
effect_correct_indentation(
left,
whitespace,
right,
&mut edits,
self.indent_level + 1,
);
}
(_, _) if is_bin_op(cooked_right) => {
effect_single_space(left, whitespace, right, &mut edits);
}
Expand All @@ -431,6 +469,27 @@ impl<'a> Formatter<'a> {
edits
}

fn update_spec_decl_state(&mut self, left_kind: &ConcreteTokenKind) {
use qsc_frontend::keyword::Keyword;
use ConcreteTokenKind::*;
use TokenKind::*;

match left_kind {
Comment => {
// Comments don't update state
}
Syntax(Keyword(Keyword::Body | Keyword::Adjoint | Keyword::Controlled)) => {
self.spec_decl_state = SpecDeclState::OnSpecKeyword;
}
Syntax(DotDotDot) if matches!(self.spec_decl_state, SpecDeclState::OnSpecKeyword) => {
self.spec_decl_state = SpecDeclState::OnEllipse;
}
_ => {
self.spec_decl_state = SpecDeclState::NoState;
}
}
}

/// Updates the type_param_state of the FormatterState based
/// on the left and right token kinds. Returns the delimiter
/// state of the left and right tokens.
Expand Down Expand Up @@ -586,10 +645,6 @@ fn is_bin_op(cooked: &TokenKind) -> bool {
| TokenKind::WSlashEq
| TokenKind::Keyword(Keyword::And)
| TokenKind::Keyword(Keyword::Or)
// Technically the rest are not binary ops, but has the same spacing as one
| TokenKind::Keyword(Keyword::Not)
| TokenKind::Keyword(Keyword::AdjointUpper)
| TokenKind::Keyword(Keyword::ControlledUpper)
)
}

Expand All @@ -614,6 +669,11 @@ fn is_suffix(cooked: &TokenKind) -> bool {
matches!(cooked, TokenKind::Bang | TokenKind::Comma)
}

fn is_prefix_keyword(keyword: &Keyword) -> bool {
use Keyword::*;
matches!(keyword, Not | AdjointUpper | ControlledUpper)
}

fn is_keyword_value(keyword: &Keyword) -> bool {
use Keyword::*;
matches!(
Expand Down Expand Up @@ -649,6 +709,17 @@ fn is_newline_keyword_or_ampersat(cooked: &TokenKind) -> bool {
)
}

fn is_starter_keyword(keyword: &Keyword) -> bool {
use Keyword::*;
matches!(keyword, For | While | Repeat | If | Within | Return | Fail)
}

fn is_newline_after_brace(cooked: &TokenKind, delim_state: Delimiter) -> bool {
is_value_token_right(cooked, delim_state)
|| matches!(cooked, TokenKind::Keyword(keyword) if is_starter_keyword(keyword))
|| matches!(cooked, TokenKind::Keyword(keyword) if is_prefix_keyword(keyword))
}

/// Note that this does not include interpolated string literals
fn is_value_lit(cooked: &TokenKind) -> bool {
matches!(
Expand Down Expand Up @@ -728,33 +799,6 @@ fn effect_trim_comment(left: &ConcreteToken, edits: &mut Vec<TextEdit>, code: &s
}
}

fn effect_trim_whitespace(
left: &ConcreteToken,
whitespace: &str,
right: &ConcreteToken,
edits: &mut Vec<TextEdit>,
) {
let count_newlines = whitespace.chars().filter(|c| *c == '\n').count();
let suffix = match whitespace.rsplit_once('\n') {
Some((_, suffix)) => suffix,
None => "",
};

let mut new_whitespace = if whitespace.contains("\r\n") {
"\r\n".repeat(count_newlines)
} else {
"\n".repeat(count_newlines)
};
new_whitespace.push_str(suffix);
if whitespace != new_whitespace {
edits.push(TextEdit::new(
new_whitespace.as_str(),
left.span.hi,
right.span.lo,
));
}
}

fn effect_correct_indentation(
left: &ConcreteToken,
whitespace: &str,
Expand Down
Loading

0 comments on commit 4d93cad

Please sign in to comment.