Skip to content

Commit 3c15bf7

Browse files
committed
feat: basic (biased) formatter
1 parent aada70a commit 3c15bf7

13 files changed

Lines changed: 1368 additions & 27 deletions

File tree

package.json

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,52 @@
215215
"default": true,
216216
"description": "Enable the SaC language server."
217217
},
218+
"sac.features.languageServer.enable": {
219+
"type": "boolean",
220+
"default": true,
221+
"description": "Enable language server feature module. Overrides sac.languageServer.enable when set."
222+
},
223+
"sac.features.formatter.enable": {
224+
"type": "boolean",
225+
"default": true,
226+
"description": "Enable formatter feature module registration."
227+
},
228+
"sac.features.chatParticipant.enable": {
229+
"type": "boolean",
230+
"default": true,
231+
"description": "Enable @sac chat participant feature module."
232+
},
233+
"sac.format.enable": {
234+
"type": "boolean",
235+
"default": true,
236+
"description": "Enable SaC document and range formatter."
237+
},
238+
"sac.format.onSave": {
239+
"type": "boolean",
240+
"default": false,
241+
"description": "Format SaC file on save using extension formatter."
242+
},
243+
"sac.format.indentSize": {
244+
"type": "number",
245+
"default": 4,
246+
"minimum": 2,
247+
"description": "Indent size used by SaC formatter."
248+
},
249+
"sac.format.normalizeGuards": {
250+
"type": "boolean",
251+
"default": true,
252+
"description": "Normalize guard prefixes to '| ' and ', ' with consistent spacing."
253+
},
254+
"sac.format.expandInlineWithLoops": {
255+
"type": "boolean",
256+
"default": true,
257+
"description": "Expand inline with-loop bodies to multiline style when formatting."
258+
},
259+
"sac.format.expandInlineComprehensions": {
260+
"type": "boolean",
261+
"default": true,
262+
"description": "Expand inline tensor comprehensions to multiline style when formatting."
263+
},
218264
"sac.diagnostics.mode": {
219265
"type": "string",
220266
"enum": [

src/constants/regex.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,16 @@ export const DOC_TAG_EXAMPLE_PATTERN = /^@example\s*(.*)$/;
4949
export const RETURN_KEYWORD_PATTERN = /\breturn\b/;
5050

5151
export const CONTROL_FLOW_KEYWORD_PATTERN = /\b(if|for|while|switch)\b/;
52+
53+
export const SINGLE_LINE_COMMENT_PATTERN = /^\s*\/\//;
54+
55+
export const INLINE_WITH_LOOP_PATTERN = /^return\s+with\s*\{\s*(\([^:]+\))\s*:\s*(.+?)\s*;\s*\}\s*:\s*genarray\s*\((.+)\)\s*;\s*$/;
56+
57+
export const INLINE_TENSOR_COMPREHENSION_PATTERN = /^return\s*\{\s*(\[[^\]]+\])\s*->\s*(.+?)\s*\|\s*(.+)\s*\}\s*;\s*$/;
58+
59+
export const TENSOR_RETURN_BLOCK_START_PATTERN = /^return\s*\{$/;
60+
61+
export const TENSOR_RETURN_BLOCK_END_PATTERN = /^\};$/;
62+
63+
export const TENSOR_ARROW_PATTERN = /->/;
64+

src/copilot/skills/sac-style-guide/SKILL.md

Lines changed: 69 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,44 +7,78 @@ description: Enforce SaC code formatting and readability conventions.
77

88
Use this skill when editing `.sac` files.
99

10-
- Use consistent indentation and brace style.
11-
- Preserve readability around shapes, signatures, and type-rich declarations.
10+
- Use spaces, not tabs.
11+
- Use 4-space indentation by default.
12+
- Preserve original trailing newline count at EOF.
1213
- Keep edits minimal and local, especially in overloaded function families.
13-
- Explain style-motivated refactors in one sentence.
14+
15+
## Formatter-parity rules
16+
17+
Follow these rules exactly to match formatter behavior.
18+
19+
1. Guard formatting
20+
- Split multi-guard function signatures into guard lines.
21+
- First guard line starts with `| `.
22+
- Additional top-level guards start with `, ` on following lines.
23+
- Do not split commas inside nested `()`, `[]`, `{}`.
24+
- Keep logical continuations (`||`, `&&`) on same guard line when they continue a guard expression.
25+
26+
2. Comments
27+
- Keep `//` comments intact; never reinterpret commented code as active code.
28+
- Normalize comment prefix spacing to `// ` when comment body exists.
29+
30+
3. Tensor comprehensions
31+
- Inline tensor comprehensions inside larger expressions should stay inline when already clear.
32+
- For a top-level `return { ... };` tensor block:
33+
- One clause: use one-line form.
34+
- Multiple clauses: one clause per line, align `->` and `|` columns.
35+
36+
4. With-loop formatting
37+
- Inline `return with { ... } : genarray(...);` may be expanded to multiline canonical form.
38+
- Keep `} : genarray(...)` arm aligned with the `with` keyword column.
39+
40+
5. Doc comments
41+
- Keep doc blocks aligned with one leading space before `*` lines and closing `*/` relative to declaration indentation.
1442

1543
## Guard formatting example
1644

17-
Use this formatting for multi-line function guards, function signatures, sacDocs, and simple tensor comprehensions.
45+
Use this formatting for multi-line function guards and nested expression continuations.
1846

1947
```sac
2048
/**
21-
* A rank-polymorphic selection function that returns a sub-array.
22-
* It uses type patterns to match dimensionality and a vertical bar (|)
23-
* to enforce explicit domain constraints.
24-
* The first guard line starts with `| ` and subsequent lines start with `, `.
25-
*
26-
* @param iv An index vector specifying which elements to select, must be non-negative and within bounds.
27-
* @param a The input array from which to select elements, must have compatible shape.
28-
* @return A new array containing the selected elements based on the index vector.
49+
* Rank-polymorphic selection with explicit domain checks.
50+
*
51+
* @param iv Index vector.
52+
* @param a Input array.
53+
* @return Selected sub-array.
2954
*/
30-
int[m:ishp] mySel (int[n] iv, int[n:shp, m:ishp] a)
31-
| all (0 <= iv) /* Guard: The index vector must be non-negative */
32-
, all (iv < shp) /* Guard: The index must be within the bounds of 'shp' */
55+
int[m:ishp] mySel(int[n] iv, int[n:shp, m:ishp] a)
56+
| all(0 <= iv)
57+
, all(iv < shp)
58+
, ((n > m && ishp == []) || (ishp == shp))
3359
{
34-
return { jv -> _sel_VxA_(++ (iv, jv), a) | jv < ishp };
60+
return { jv -> _sel_VxA_(++(iv, jv), a) | jv < ishp };
3561
}
3662
```
3763

38-
Use multi-line style for with-loops and complex tensor comprehensions as well.
39-
4064
## Big with-loop example
4165

4266
```sac
4367
int[m, n] checkerboard(int[m, n] a)
4468
{
45-
return with {
46-
( . <= [i, j] <= . ) : (a[i, j] + i + j) % 2;
47-
} : genarray([m, n], 0);
69+
return with {
70+
(. <= [i, j] <= .) : (a[i, j] + i + j) % 2;
71+
} : genarray([m, n], 0);
72+
}
73+
```
74+
75+
## Inline tensor comprehension in expression
76+
77+
```sac
78+
int[d:shp] vsum2(int[n, d:shp] a)
79+
{
80+
transposed_a = transpose(a);
81+
return d > 0 ? transpose({ iv -> sum(transposed_a[iv]) | iv < reverse(shp) }) : sum(a);
4882
}
4983
```
5084

@@ -53,9 +87,18 @@ int[m, n] checkerboard(int[m, n] a)
5387
```sac
5488
int[m, n] edgeMask(int[m, n] a)
5589
{
56-
return {
57-
[i, j] -> abs(a[i, j] - a[i, j - 1]) + abs(a[i, j] - a[i - 1, j]) | [14] <= [i, j] < [m - 1, n - 1];
58-
[i, j] -> 0 | [i, j] < [m, n]
59-
};
90+
return {
91+
[i, j] -> abs(a[i, j] - a[i, j - 1]) + abs(a[i, j] - a[i - 1, j]) | [1, 1] <= [i, j] < [m - 1, n - 1];
92+
[i, j] -> 0 | [i, j] < [m, n]
93+
};
6094
}
61-
```
95+
```
96+
97+
## `.sac-format` keys
98+
99+
When present, align behavior with these keys:
100+
- `IndentSize`
101+
- `TabWidth`
102+
- `NormalizeGuards`
103+
- `ExpandInlineWithLoops`
104+
- `ExpandInlineComprehensions`

src/extension/features/chatParticipantFeature.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ type SacChatMetadata = {
88
command: string;
99
};
1010

11+
/**
12+
* Builds chat result metadata used by follow-up provider.
13+
*
14+
* @param command Executed command name.
15+
* @returns Chat result with metadata payload.
16+
*/
1117
function chatResult(command: string): vscode.ChatResult {
1218
return {
1319
metadata: {
@@ -16,6 +22,12 @@ function chatResult(command: string): vscode.ChatResult {
1622
};
1723
}
1824

25+
/**
26+
* Builds default participant response when no command is provided.
27+
*
28+
* @param prompt User prompt text.
29+
* @returns Markdown response text.
30+
*/
1931
function buildDefaultResponse(prompt: string): string {
2032
const normalizedPrompt = prompt.trim();
2133
if (normalizedPrompt.length === 0) {
@@ -39,7 +51,15 @@ function buildDefaultResponse(prompt: string): string {
3951
export class ChatParticipantFeature implements FeatureLifecycle {
4052
private participant: vscode.ChatParticipant | undefined;
4153

54+
/**
55+
* Registers chat participant and command handlers.
56+
*/
4257
public async activate(): Promise<void> {
58+
const enabled = vscode.workspace.getConfiguration("sac").get<boolean>("features.chatParticipant.enable", true);
59+
if (!enabled) {
60+
return;
61+
}
62+
4363
const handler: vscode.ChatRequestHandler = async (request, _context, stream): Promise<vscode.ChatResult> => {
4464
if (request.command === "sac-diagnose") {
4565
stream.markdown("SaC diagnostics workflow:\n\n1. Run `sac2c` with minimal flags first.\n2. Fix the first parser/type error before secondary errors.\n3. Re-run and iterate until the first blocking error is resolved.");
@@ -82,6 +102,9 @@ export class ChatParticipantFeature implements FeatureLifecycle {
82102
};
83103
}
84104

105+
/**
106+
* Disposes chat participant registration.
107+
*/
85108
public async deactivate(): Promise<void> {
86109
this.participant?.dispose();
87110
this.participant = undefined;

0 commit comments

Comments
 (0)