Skip to content

Commit 0dd12ad

Browse files
committed
pattern, dsl, rules
1 parent a01fb85 commit 0dd12ad

File tree

5 files changed

+355
-13
lines changed

5 files changed

+355
-13
lines changed

crates/ra_fmt/src/dsl.rs

Lines changed: 198 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,198 @@
1-
pub struct SpacingDsl {}
1+
use crate::{pattern::{Pattern, PatternSet}};
2+
use ra_syntax::{SyntaxElement,};
3+
use std::iter::successors;
4+
5+
/// `SpacingRule` describes whitespace requirements between `SyntaxElement` Note
6+
/// that it doesn't handle indentation (first whitespace on a line), there's
7+
/// `IndentRule` for that!
8+
#[derive(Debug)]
9+
pub(crate) struct SpacingRule {
10+
/// An element to which this spacing rule applies
11+
pub(crate) pattern: Pattern,
12+
/// How much space to add/remove at the start or end of the element.
13+
pub(crate) space: Space,
14+
}
15+
16+
/// Make `SpacingRule` usable with `PatternSet`
17+
impl AsRef<Pattern> for SpacingRule {
18+
fn as_ref(&self) -> &Pattern {
19+
&self.pattern
20+
}
21+
}
22+
23+
#[derive(Debug, Clone, Copy)]
24+
pub(crate) struct Space {
25+
/// How much space to add.
26+
pub(crate) value: SpaceValue,
27+
/// Should the space be added before, after or around the element?
28+
pub(crate) loc: SpaceLoc,
29+
}
30+
31+
#[derive(Debug, Clone, Copy)]
32+
pub(crate) enum SpaceValue {
33+
/// Single whitespace char, like ` `
34+
Single,
35+
/// Single whitespace char, like ` `, but preserve existing line break.
36+
SingleOptionalNewline,
37+
/// A single newline (`\n`) char
38+
Newline,
39+
/// No whitespace at all.
40+
None,
41+
/// No space, but preserve existing line break.
42+
NoneOptionalNewline,
43+
/// If the parent element fits into a single line, a single space.
44+
/// Otherwise, at least one newline.
45+
/// Existing newlines are preserved.
46+
SingleOrNewline,
47+
/// If the parent element fits into a single line, no space.
48+
/// Otherwise, at least one newline.
49+
/// Existing newlines are preserved.
50+
NoneOrNewline,
51+
}
52+
53+
#[derive(Debug, Clone, Copy)]
54+
pub(crate) enum SpaceLoc {
55+
/// Before the element.
56+
Before,
57+
/// After the element.
58+
After,
59+
/// On the both sides of the element.
60+
Around,
61+
}
62+
63+
/// A builder to conveniently specify a set of `SpacingRule`s
64+
#[derive(Debug, Default)]
65+
pub(crate) struct SpacingDsl {
66+
pub(crate) rules: Vec<SpacingRule>,
67+
#[cfg(test)]
68+
pub(crate) tests: Vec<(&'static str, &'static str)>,
69+
}
70+
71+
impl SpacingDsl {
72+
pub(crate) fn rule(&mut self, rule: SpacingRule) -> &mut SpacingDsl {
73+
self.rules.push(rule);
74+
self
75+
}
76+
77+
pub(crate) fn inside(&mut self, parent: impl Into<Pattern>) -> SpacingRuleBuilder {
78+
SpacingRuleBuilder {
79+
dsl: self,
80+
parent: parent.into(),
81+
child: None,
82+
between: None,
83+
loc: None,
84+
}
85+
}
86+
}
87+
/// A builder to conveniently specify a single rule.
88+
pub(crate) struct SpacingRuleBuilder<'a> {
89+
dsl: &'a mut SpacingDsl,
90+
parent: Pattern,
91+
child: Option<Pattern>,
92+
between: Option<(Pattern, Pattern)>,
93+
loc: Option<SpaceLoc>,
94+
}
95+
96+
impl<'a> SpacingRuleBuilder<'a> {
97+
/// The rule applies to both sides of the element `child`.
98+
pub(crate) fn around(mut self, child: impl Into<Pattern>) -> SpacingRuleBuilder<'a> {
99+
self.child = Some(child.into());
100+
self.loc = Some(SpaceLoc::Around);
101+
self
102+
}
103+
/// The rule applies to the leading whitespace before `child`.
104+
pub(crate) fn before(mut self, child: impl Into<Pattern>) -> SpacingRuleBuilder<'a> {
105+
self.child = Some(child.into());
106+
self.loc = Some(SpaceLoc::Before);
107+
self
108+
}
109+
/// The rule applies to the trailing whitespace after `child`.
110+
pub(crate) fn after(mut self, child: impl Into<Pattern>) -> SpacingRuleBuilder<'a> {
111+
self.child = Some(child.into());
112+
self.loc = Some(SpaceLoc::After);
113+
self
114+
}
115+
/// The rule applies to the whitespace between the two nodes.
116+
pub(crate) fn between(
117+
mut self,
118+
left: impl Into<Pattern>,
119+
right: impl Into<Pattern>,
120+
) -> SpacingRuleBuilder<'a> {
121+
self.between = Some((left.into(), right.into()));
122+
self.loc = Some(SpaceLoc::After);
123+
self
124+
}
125+
/// The rule applies if the `cond` is true.
126+
pub(crate) fn when(mut self, cond: fn(&SyntaxElement) -> bool) -> SpacingRuleBuilder<'a> {
127+
let pred = cond.into();
128+
let prev = self.child.take().unwrap();
129+
self.child = Some(prev & pred);
130+
self
131+
}
132+
/// Enforce single whitespace character.
133+
pub(crate) fn single_space(self) -> &'a mut SpacingDsl {
134+
self.finish(SpaceValue::Single)
135+
}
136+
pub(crate) fn single_space_or_optional_newline(self) -> &'a mut SpacingDsl {
137+
self.finish(SpaceValue::SingleOptionalNewline)
138+
}
139+
pub(crate) fn no_space_or_optional_newline(self) -> &'a mut SpacingDsl {
140+
self.finish(SpaceValue::NoneOptionalNewline)
141+
}
142+
/// Enforce the absence of any space.
143+
pub(crate) fn no_space(self) -> &'a mut SpacingDsl {
144+
self.finish(SpaceValue::None)
145+
}
146+
/// Enforce a single whitespace or newline character.
147+
pub(crate) fn single_space_or_newline(self) -> &'a mut SpacingDsl {
148+
self.finish(SpaceValue::SingleOrNewline)
149+
}
150+
/// Enforce a absence of whitespace or a newline character.
151+
pub(crate) fn no_space_or_newline(self) -> &'a mut SpacingDsl {
152+
self.finish(SpaceValue::NoneOrNewline)
153+
}
154+
fn finish(self, value: SpaceValue) -> &'a mut SpacingDsl {
155+
assert!(self.between.is_some() ^ self.child.is_some());
156+
if let Some((left, right)) = self.between {
157+
let child = {
158+
let left = left.clone();
159+
let right = right.clone();
160+
left & Pattern::from(move |it: &SyntaxElement| {
161+
next_non_whitespace_sibling(it).map(|it| right.matches(&it)) == Some(true)
162+
})
163+
};
164+
let rule = SpacingRule {
165+
pattern: child.with_parent(self.parent.clone()),
166+
space: Space { value, loc: SpaceLoc::After },
167+
};
168+
self.dsl.rule(rule);
169+
170+
let child = right
171+
& Pattern::from(move |it: &SyntaxElement| {
172+
prev_non_whitespace_sibling(it).map(|it| left.matches(&it)) == Some(true)
173+
});
174+
let rule = SpacingRule {
175+
pattern: child.with_parent(self.parent),
176+
space: Space { value, loc: SpaceLoc::Before },
177+
};
178+
self.dsl.rule(rule);
179+
} else {
180+
let rule = SpacingRule {
181+
pattern: self.child.unwrap().with_parent(self.parent),
182+
space: Space { value, loc: self.loc.unwrap() },
183+
};
184+
self.dsl.rule(rule);
185+
}
186+
self.dsl
187+
}
188+
}
189+
190+
pub(crate) fn prev_non_whitespace_sibling(element: &SyntaxElement) -> Option<SyntaxElement> {
191+
successors(element.prev_sibling_or_token(), |it| it.prev_sibling_or_token())
192+
.find(|it| it.kind() != TOKEN_WHITESPACE)
193+
}
194+
195+
pub(crate) fn next_non_whitespace_sibling(element: &SyntaxElement) -> Option<SyntaxElement> {
196+
successors(element.next_sibling_or_token(), |it| it.next_sibling_or_token())
197+
.find(|it| it.kind() != TOKEN_WHITESPACE)
198+
}

crates/ra_fmt/src/engine.rs

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,30 @@
11
use crate::dsl;
2-
32
use ra_syntax::{
43
ast::{self, AstNode, AstToken},
54
Parse, SmolStr, SourceFile,
65
SyntaxElement, TextUnit,
76
SyntaxKind, SyntaxKind::*,
87
SyntaxNode, SyntaxToken, T,
9-
WalkEvent,
8+
WalkEvent, TextRange,
109
};
10+
use std::collections::HashMap;
1111

1212
#[derive(Debug)]
1313

1414
struct FmtTree {
1515
/// owned original root node
16-
original: SyntaxNode,
16+
original_node: SyntaxNode,
17+
/// We store `SpaceBlock`s in array. With this setup, we can refer to a
18+
/// specific block by index, dodging many lifetime issues.
1719
blocks: Vec<SpaceBlock>,
20+
/// Maps offset to an index of the block, for which the offset is the start
21+
/// offset.
22+
by_start_offset: HashMap<TextUnit, usize>,
23+
/// Maps offset to an index of the block, for which the offset is the end
24+
/// offset.
25+
by_end_offset: HashMap<TextUnit, usize>,
26+
// Arbitrary non-whitespace edits created by the last formatter phase.
27+
//fixes: Vec<AtomEdit>,
1828
}
1929

2030
#[derive(Debug)]
@@ -57,7 +67,7 @@ impl SpaceBlock {
5767
fn new(original: OriginalSpace) -> SpaceBlock {
5868
let semantic_newline = match &original {
5969
OriginalSpace::Some(token) => {
60-
token.text().contains('\n') && is_line_comment(token.prev_sibling_or_token())
70+
token.text().contains('\n')
6171
}
6272
OriginalSpace::None { .. } => false,
6373
};
@@ -96,13 +106,12 @@ impl SpaceBlock {
96106
}
97107

98108
impl FmtTree {
99-
pub(super) fn new(org_node: SyntaxNode) -> Self {
109+
pub(super) fn new(original_node: SyntaxNode) -> Self {
100110
Self {
101-
org_node,
111+
original_node,
102112
blocks: vec![],
103113
by_start_offset: HashMap::default(),
104114
by_end_offset: HashMap::default(),
105-
fixes: vec![],
106115
}
107116
}
108117
}
@@ -117,9 +126,11 @@ fn walk(node: &SyntaxNode) -> impl Iterator<Item = SyntaxElement> {
117126
})
118127
}
119128

120-
fn format_pass(rules: &dsl::SpacingDsl, root: &SyntaxNode) {
121-
129+
fn format_pass(space_rules: &dsl::SpacingDsl, root: &SyntaxNode) {
130+
let mut fmt_root = FmtTree::new(root.clone());
122131
for node in walk(root) {
123-
132+
for rule in space_rules.matching(node.clone()) {
133+
rule.apply(&node, &mut fmt_root)
134+
}
124135
}
125136
}

crates/ra_fmt/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod scratch;
44
mod rules;
55
mod dsl;
66
mod engine;
7+
mod pattern;
78

89
use itertools::Itertools;
910
use ra_syntax::{

0 commit comments

Comments
 (0)